wallet: t_s/d_us migration

This commit is contained in:
Florian Dold 2022-03-18 15:32:41 +01:00
parent 32cd54e11d
commit f8d12f7b0d
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
62 changed files with 901 additions and 1235 deletions

View File

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

View File

@ -19,8 +19,9 @@ import {
parsePayUri, parsePayUri,
stringToBytes, stringToBytes,
TalerErrorCode, TalerErrorCode,
TalerProtocolTimestamp,
TalerSignaturePurpose, TalerSignaturePurpose,
Timestamp, AbsoluteTime,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { anastasisData } from "./anastasis-data.js"; import { anastasisData } from "./anastasis-data.js";
import { import {
@ -631,15 +632,13 @@ async function uploadSecret(
logger.info(`got response for policy upload (http status ${resp.status})`); logger.info(`got response for policy upload (http status ${resp.status})`);
if (resp.status === HttpStatusCode.NoContent) { if (resp.status === HttpStatusCode.NoContent) {
let policyVersion = 0; let policyVersion = 0;
let policyExpiration: Timestamp = { t_ms: 0 }; let policyExpiration: TalerProtocolTimestamp = { t_s: 0 };
try { try {
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0"); policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
} catch (e) {} } catch (e) {}
try { try {
policyExpiration = { policyExpiration = {
t_ms: t_s: Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
1000 *
Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
}; };
} catch (e) {} } catch (e) {}
successDetails[prov.provider_url] = { successDetails[prov.provider_url] = {
@ -1187,7 +1186,7 @@ async function addProviderBackup(
state: ReducerStateBackup, state: ReducerStateBackup,
args: ActionArgsAddProvider, args: ActionArgsAddProvider,
): Promise<ReducerStateBackup> { ): Promise<ReducerStateBackup> {
const info = await getProviderInfo(args.provider_url) const info = await getProviderInfo(args.provider_url);
return { return {
...state, ...state,
authentication_providers: { authentication_providers: {
@ -1202,8 +1201,10 @@ async function deleteProviderBackup(
state: ReducerStateBackup, state: ReducerStateBackup,
args: ActionArgsDeleteProvider, args: ActionArgsDeleteProvider,
): Promise<ReducerStateBackup> { ): Promise<ReducerStateBackup> {
const authentication_providers = {... state.authentication_providers ?? {} } const authentication_providers = {
delete authentication_providers[args.provider_url] ...(state.authentication_providers ?? {}),
};
delete authentication_providers[args.provider_url];
return { return {
...state, ...state,
authentication_providers, authentication_providers,
@ -1214,7 +1215,7 @@ async function addProviderRecovery(
state: ReducerStateRecovery, state: ReducerStateRecovery,
args: ActionArgsAddProvider, args: ActionArgsAddProvider,
): Promise<ReducerStateRecovery> { ): Promise<ReducerStateRecovery> {
const info = await getProviderInfo(args.provider_url) const info = await getProviderInfo(args.provider_url);
return { return {
...state, ...state,
authentication_providers: { authentication_providers: {
@ -1228,8 +1229,10 @@ async function deleteProviderRecovery(
state: ReducerStateRecovery, state: ReducerStateRecovery,
args: ActionArgsDeleteProvider, args: ActionArgsDeleteProvider,
): Promise<ReducerStateRecovery> { ): Promise<ReducerStateRecovery> {
const authentication_providers = {... state.authentication_providers ?? {} } const authentication_providers = {
delete authentication_providers[args.provider_url] ...(state.authentication_providers ?? {}),
};
delete authentication_providers[args.provider_url];
return { return {
...state, ...state,
authentication_providers, authentication_providers,
@ -1347,7 +1350,8 @@ async function updateUploadFees(
x, x,
).amount; ).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`); logger.info(`computing fees for ${years} years`);
// For now, we compute fees for *all* available providers. // For now, we compute fees for *all* available providers.
for (const provUrl in state.authentication_providers ?? {}) { for (const provUrl in state.authentication_providers ?? {}) {

View File

@ -7,7 +7,8 @@ import {
codecForString, codecForString,
codecForTimestamp, codecForTimestamp,
Duration, Duration,
Timestamp, TalerProtocolTimestamp,
AbsoluteTime,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ChallengeFeedback } from "./challenge-feedback-types.js"; import { ChallengeFeedback } from "./challenge-feedback-types.js";
import { KeyShare } from "./crypto.js"; import { KeyShare } from "./crypto.js";
@ -43,7 +44,7 @@ export interface PolicyProvider {
export interface SuccessDetails { export interface SuccessDetails {
[provider_url: string]: { [provider_url: string]: {
policy_version: number; policy_version: number;
policy_expiration: Timestamp; policy_expiration: TalerProtocolTimestamp;
}; };
} }
@ -112,7 +113,7 @@ export interface ReducerStateBackup {
core_secret?: CoreSecret; core_secret?: CoreSecret;
expiration?: Timestamp; expiration?: TalerProtocolTimestamp;
upload_fees?: { fee: AmountString }[]; upload_fees?: { fee: AmountString }[];
@ -369,7 +370,7 @@ export interface ActionArgsEnterSecret {
value: string; value: string;
mime?: string; mime?: string;
}; };
expiration: Timestamp; expiration: TalerProtocolTimestamp;
} }
export interface ActionArgsSelectContinent { export interface ActionArgsSelectContinent {
@ -438,7 +439,7 @@ export interface ActionArgsAddPolicy {
} }
export interface ActionArgsUpdateExpiration { export interface ActionArgsUpdateExpiration {
expiration: Timestamp; expiration: TalerProtocolTimestamp;
} }
export interface ActionArgsChangeVersion { export interface ActionArgsChangeVersion {

View File

@ -51,13 +51,13 @@ export const WithDetails = createExample(TestedComponent, {
success_details: { success_details: {
"https://anastasis.demo.taler.net/": { "https://anastasis.demo.taler.net/": {
policy_expiration: { policy_expiration: {
t_ms: "never", t_s: "never",
}, },
policy_version: 0, policy_version: 0,
}, },
"https://kudos.demo.anastasis.lu/": { "https://kudos.demo.anastasis.lu/": {
policy_expiration: { policy_expiration: {
t_ms: new Date().getTime() + 60 * 60 * 24 * 1000, t_s: new Date().getTime() + 60 * 60 * 24,
}, },
policy_version: 1, policy_version: 1,
}, },

View File

@ -35,9 +35,9 @@ export function BackupFinishedScreen(): VNode {
</a> </a>
<p> <p>
version {sd.policy_version} version {sd.policy_version}
{sd.policy_expiration.t_ms !== "never" {sd.policy_expiration.t_s !== "never"
? ` expires at: ${format( ? ` expires at: ${format(
new Date(sd.policy_expiration.t_ms), new Date(sd.policy_expiration.t_s),
"dd-MM-yyyy", "dd-MM-yyyy",
)}` )}`
: " without expiration date"} : " without expiration date"}

View File

@ -52,7 +52,7 @@ export function SecretEditorScreen(): VNode {
await tx.transition("enter_secret", { await tx.transition("enter_secret", {
secret, secret,
expiration: { 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", {}); await tx.transition("next", {});

View File

@ -15,7 +15,7 @@
*/ */
import { Logger } from "./logging.js"; import { Logger } from "./logging.js";
import { getTimestampNow, timestampCmp, timestampDifference } from "./time.js"; import { AbsoluteTime } from "./time.js";
/** /**
* Implementation of token bucket throttling. * Implementation of token bucket throttling.
@ -46,16 +46,16 @@ class OriginState {
tokensSecond: number = MAX_PER_SECOND; tokensSecond: number = MAX_PER_SECOND;
tokensMinute: number = MAX_PER_MINUTE; tokensMinute: number = MAX_PER_MINUTE;
tokensHour: number = MAX_PER_HOUR; tokensHour: number = MAX_PER_HOUR;
private lastUpdate = getTimestampNow(); private lastUpdate = AbsoluteTime.now();
private refill(): void { private refill(): void {
const now = getTimestampNow(); const now = AbsoluteTime.now();
if (timestampCmp(now, this.lastUpdate) < 0) { if (AbsoluteTime.cmp(now, this.lastUpdate) < 0) {
// Did the system time change? // Did the system time change?
this.lastUpdate = now; this.lastUpdate = now;
return; return;
} }
const d = timestampDifference(now, this.lastUpdate); const d = AbsoluteTime.difference(now, this.lastUpdate);
if (d.d_ms === "forever") { if (d.d_ms === "forever") {
throw Error("assertion failed"); throw Error("assertion failed");
} }

View File

@ -38,7 +38,11 @@ import {
EddsaPublicKeyString, EddsaPublicKeyString,
CoinPublicKeyString, CoinPublicKeyString,
} from "./talerTypes"; } from "./talerTypes";
import { Timestamp, codecForTimestamp } from "./time.js"; import {
AbsoluteTime,
codecForTimestamp,
TalerProtocolTimestamp,
} from "./time.js";
export enum ReserveTransactionType { export enum ReserveTransactionType {
Withdraw = "WITHDRAW", Withdraw = "WITHDRAW",
@ -98,7 +102,7 @@ export interface ReserveCreditTransaction {
/** /**
* Timestamp of the incoming wire transfer. * Timestamp of the incoming wire transfer.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
} }
export interface ReserveClosingTransaction { export interface ReserveClosingTransaction {
@ -139,7 +143,7 @@ export interface ReserveClosingTransaction {
/** /**
* Time when the reserve was closed. * Time when the reserve was closed.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
} }
export interface ReserveRecoupTransaction { export interface ReserveRecoupTransaction {
@ -165,7 +169,7 @@ export interface ReserveRecoupTransaction {
/** /**
* Time when the funds were paid back into the reserve. * Time when the funds were paid back into the reserve.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
/** /**
* Public key of the coin that was paid back. * Public key of the coin that was paid back.
@ -182,46 +186,50 @@ export type ReserveTransaction =
| ReserveClosingTransaction | ReserveClosingTransaction
| ReserveRecoupTransaction; | ReserveRecoupTransaction;
export const codecForReserveWithdrawTransaction = (): Codec<ReserveWithdrawTransaction> => export const codecForReserveWithdrawTransaction =
buildCodecForObject<ReserveWithdrawTransaction>() (): Codec<ReserveWithdrawTransaction> =>
.property("amount", codecForString()) buildCodecForObject<ReserveWithdrawTransaction>()
.property("h_coin_envelope", codecForString()) .property("amount", codecForString())
.property("h_denom_pub", codecForString()) .property("h_coin_envelope", codecForString())
.property("reserve_sig", codecForString()) .property("h_denom_pub", codecForString())
.property("type", codecForConstString(ReserveTransactionType.Withdraw)) .property("reserve_sig", codecForString())
.property("withdraw_fee", codecForString()) .property("type", codecForConstString(ReserveTransactionType.Withdraw))
.build("ReserveWithdrawTransaction"); .property("withdraw_fee", codecForString())
.build("ReserveWithdrawTransaction");
export const codecForReserveCreditTransaction = (): Codec<ReserveCreditTransaction> => export const codecForReserveCreditTransaction =
buildCodecForObject<ReserveCreditTransaction>() (): Codec<ReserveCreditTransaction> =>
.property("amount", codecForString()) buildCodecForObject<ReserveCreditTransaction>()
.property("sender_account_url", codecForString()) .property("amount", codecForString())
.property("timestamp", codecForTimestamp) .property("sender_account_url", codecForString())
.property("wire_reference", codecForNumber()) .property("timestamp", codecForTimestamp)
.property("type", codecForConstString(ReserveTransactionType.Credit)) .property("wire_reference", codecForNumber())
.build("ReserveCreditTransaction"); .property("type", codecForConstString(ReserveTransactionType.Credit))
.build("ReserveCreditTransaction");
export const codecForReserveClosingTransaction = (): Codec<ReserveClosingTransaction> => export const codecForReserveClosingTransaction =
buildCodecForObject<ReserveClosingTransaction>() (): Codec<ReserveClosingTransaction> =>
.property("amount", codecForString()) buildCodecForObject<ReserveClosingTransaction>()
.property("closing_fee", codecForString()) .property("amount", codecForString())
.property("exchange_pub", codecForString()) .property("closing_fee", codecForString())
.property("exchange_sig", codecForString()) .property("exchange_pub", codecForString())
.property("h_wire", codecForString()) .property("exchange_sig", codecForString())
.property("timestamp", codecForTimestamp) .property("h_wire", codecForString())
.property("type", codecForConstString(ReserveTransactionType.Closing)) .property("timestamp", codecForTimestamp)
.property("wtid", codecForString()) .property("type", codecForConstString(ReserveTransactionType.Closing))
.build("ReserveClosingTransaction"); .property("wtid", codecForString())
.build("ReserveClosingTransaction");
export const codecForReserveRecoupTransaction = (): Codec<ReserveRecoupTransaction> => export const codecForReserveRecoupTransaction =
buildCodecForObject<ReserveRecoupTransaction>() (): Codec<ReserveRecoupTransaction> =>
.property("amount", codecForString()) buildCodecForObject<ReserveRecoupTransaction>()
.property("coin_pub", codecForString()) .property("amount", codecForString())
.property("exchange_pub", codecForString()) .property("coin_pub", codecForString())
.property("exchange_sig", codecForString()) .property("exchange_pub", codecForString())
.property("timestamp", codecForTimestamp) .property("exchange_sig", codecForString())
.property("type", codecForConstString(ReserveTransactionType.Recoup)) .property("timestamp", codecForTimestamp)
.build("ReserveRecoupTransaction"); .property("type", codecForConstString(ReserveTransactionType.Recoup))
.build("ReserveRecoupTransaction");
export const codecForReserveTransaction = (): Codec<ReserveTransaction> => export const codecForReserveTransaction = (): Codec<ReserveTransaction> =>
buildCodecForUnion<ReserveTransaction>() buildCodecForUnion<ReserveTransaction>()

View File

@ -54,7 +54,7 @@
* Imports. * Imports.
*/ */
import { DenominationPubKey, UnblindedSignature } from "./talerTypes.js"; 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. * 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 * This timestamp should only be advanced if the content
* of the backup changes. * of the backup changes.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
/** /**
* Per-exchange data sorted by exchange master public key. * Per-exchange data sorted by exchange master public key.
@ -333,10 +333,10 @@ export interface BackupRecoupGroup {
/** /**
* Timestamp when the recoup was started. * Timestamp when the recoup was started.
*/ */
timestamp_created: Timestamp; timestamp_created: TalerProtocolTimestamp;
timestamp_finish?: Timestamp; timestamp_finish?: TalerProtocolTimestamp;
finish_clock?: Timestamp; finish_clock?: TalerProtocolTimestamp;
finish_is_failure?: boolean; 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 * Has the user accepted the tip? Only after the tip has been accepted coins
* withdrawn from the tip may be used. * withdrawn from the tip may be used.
*/ */
timestamp_accepted: Timestamp | undefined; timestamp_accepted: TalerProtocolTimestamp | undefined;
/** /**
* When was the tip first scanned by the wallet? * 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; finish_is_failure?: boolean;
/** /**
@ -504,7 +504,7 @@ export interface BackupTip {
/** /**
* Timestamp, the tip can't be picked up anymore after this deadline. * 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. * The exchange that will sign our coins, chosen by the merchant.
@ -613,9 +613,9 @@ export interface BackupRefreshGroup {
*/ */
old_coins: BackupRefreshOldCoin[]; old_coins: BackupRefreshOldCoin[];
timestamp_created: Timestamp; timestamp_created: TalerProtocolTimestamp;
timestamp_finish?: Timestamp; timestamp_finish?: TalerProtocolTimestamp;
finish_is_failure?: boolean; finish_is_failure?: boolean;
} }
@ -636,9 +636,9 @@ export interface BackupWithdrawalGroup {
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
timestamp_created: Timestamp; timestamp_created: TalerProtocolTimestamp;
timestamp_finish?: Timestamp; timestamp_finish?: TalerProtocolTimestamp;
finish_is_failure?: boolean; finish_is_failure?: boolean;
/** /**
@ -672,12 +672,12 @@ export interface BackupRefundItemCommon {
/** /**
* Execution time as claimed by the merchant * Execution time as claimed by the merchant
*/ */
execution_time: Timestamp; execution_time: TalerProtocolTimestamp;
/** /**
* Time when the wallet became aware of the refund. * Time when the wallet became aware of the refund.
*/ */
obtained_time: Timestamp; obtained_time: TalerProtocolTimestamp;
/** /**
* Amount refunded for the coin. * Amount refunded for the coin.
@ -788,7 +788,7 @@ export interface BackupPurchase {
* Timestamp of the first time that sending a payment to the merchant * Timestamp of the first time that sending a payment to the merchant
* for this purchase was successful. * for this purchase was successful.
*/ */
timestamp_first_successful_pay: Timestamp | undefined; timestamp_first_successful_pay: TalerProtocolTimestamp | undefined;
/** /**
* Signature by the merchant confirming the payment. * Signature by the merchant confirming the payment.
@ -799,7 +799,7 @@ export interface BackupPurchase {
* When was the purchase made? * When was the purchase made?
* Refers to the time that the user accepted. * Refers to the time that the user accepted.
*/ */
timestamp_accept: Timestamp; timestamp_accept: TalerProtocolTimestamp;
/** /**
* Pending refunds for the purchase. A refund is pending * 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. * 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. * Validity start date of the denomination.
*/ */
stamp_start: Timestamp; stamp_start: TalerProtocolTimestamp;
/** /**
* Date after which the currency can't be withdrawn anymore. * 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. * 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. * 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 * 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 * The list issue date of the exchange "/keys" response
* that this denomination was last seen in. * 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. * Time when the reserve was created.
*/ */
timestamp_created: Timestamp; timestamp_created: TalerProtocolTimestamp;
/** /**
* Timestamp of the last observed activity. * Timestamp of the last observed activity.
* *
* Used to compute when to give up querying the exchange. * Used to compute when to give up querying the exchange.
*/ */
timestamp_last_activity: Timestamp; timestamp_last_activity: TalerProtocolTimestamp;
/** /**
* Timestamp of when the reserve closed. * Timestamp of when the reserve closed.
@ -938,7 +938,7 @@ export interface BackupReserve {
* Note that the last activity can be after the closing time * Note that the last activity can be after the closing time
* due to recouping. * due to recouping.
*/ */
timestamp_closed?: Timestamp; timestamp_closed?: TalerProtocolTimestamp;
/** /**
* Wire information (as payto URI) for the bank account that * 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. * 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. * Time when the reserve was confirmed by the bank.
* *
* Set to undefined if not confirmed yet. * 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 date of the fee.
*/ */
start_stamp: Timestamp; start_stamp: TalerProtocolTimestamp;
/** /**
* End date of the fee. * End date of the fee.
*/ */
end_stamp: Timestamp; end_stamp: TalerProtocolTimestamp;
/** /**
* Signature made by the exchange master key. * Signature made by the exchange master key.
@ -1050,9 +1050,9 @@ export interface BackupExchangeWireFee {
* Structure of one exchange signing key in the /keys response. * Structure of one exchange signing key in the /keys response.
*/ */
export class BackupExchangeSignKey { export class BackupExchangeSignKey {
stamp_start: Timestamp; stamp_start: TalerProtocolTimestamp;
stamp_expire: Timestamp; stamp_expire: TalerProtocolTimestamp;
stamp_end: Timestamp; stamp_end: TalerProtocolTimestamp;
key: string; key: string;
master_sig: string; master_sig: string;
} }
@ -1112,7 +1112,7 @@ export interface BackupExchange {
* *
* Used to facilitate automatic merging. * Used to facilitate automatic merging.
*/ */
update_clock: Timestamp; update_clock: TalerProtocolTimestamp;
} }
/** /**
@ -1161,7 +1161,7 @@ export interface BackupExchangeDetails {
/** /**
* Closing delay of reserves. * Closing delay of reserves.
*/ */
reserve_closing_delay: Duration; reserve_closing_delay: TalerProtocolDuration;
/** /**
* Signing keys we got from the exchange, can also contain * Signing keys we got from the exchange, can also contain
@ -1187,7 +1187,7 @@ export interface BackupExchangeDetails {
/** /**
* Timestamp when the ToS has been accepted. * Timestamp when the ToS has been accepted.
*/ */
tos_accepted_timestamp: Timestamp | undefined; tos_accepted_timestamp: TalerProtocolTimestamp | undefined;
} }
export enum BackupProposalStatus { export enum BackupProposalStatus {
@ -1248,7 +1248,7 @@ export interface BackupProposal {
* Timestamp of when the record * Timestamp of when the record
* was created. * was created.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
/** /**
* Private key for the nonce. * Private key for the nonce.

View File

@ -38,15 +38,14 @@ import {
codecForConstNumber, codecForConstNumber,
buildCodecForUnion, buildCodecForUnion,
codecForConstString, codecForConstString,
codecForEither,
} from "./codec.js"; } from "./codec.js";
import { import {
Timestamp,
codecForTimestamp, codecForTimestamp,
Duration,
codecForDuration, codecForDuration,
TalerProtocolTimestamp,
TalerProtocolDuration,
} from "./time.js"; } from "./time.js";
import { Amounts, codecForAmountString } from "./amounts.js"; import { codecForAmountString } from "./amounts.js";
import { strcmp } from "./helpers.js"; import { strcmp } from "./helpers.js";
/** /**
@ -86,24 +85,24 @@ export class ExchangeDenomination {
/** /**
* Start date from which withdraw is allowed. * Start date from which withdraw is allowed.
*/ */
stamp_start: Timestamp; stamp_start: TalerProtocolTimestamp;
/** /**
* End date for withdrawing. * End date for withdrawing.
*/ */
stamp_expire_withdraw: Timestamp; stamp_expire_withdraw: TalerProtocolTimestamp;
/** /**
* Expiration date after which the exchange can forget about * Expiration date after which the exchange can forget about
* the currency. * the currency.
*/ */
stamp_expire_legal: Timestamp; stamp_expire_legal: TalerProtocolTimestamp;
/** /**
* Date after which the coins of this denomination can't be * Date after which the coins of this denomination can't be
* deposited anymore. * deposited anymore.
*/ */
stamp_expire_deposit: Timestamp; stamp_expire_deposit: TalerProtocolTimestamp;
/** /**
* Signature over the denomination information by the exchange's master * Signature over the denomination information by the exchange's master
@ -394,7 +393,7 @@ export interface Product {
taxes?: Tax[]; taxes?: Tax[];
// time indicating when this product should be delivered // time indicating when this product should be delivered
delivery_date?: Timestamp; delivery_date?: TalerProtocolTimestamp;
} }
export interface InternationalizedString { export interface InternationalizedString {
@ -413,7 +412,7 @@ export interface ContractTerms {
/** /**
* Hash of the merchant's wire details. * Hash of the merchant's wire details.
*/ */
auto_refund?: Duration; auto_refund?: TalerProtocolDuration;
/** /**
* Wire method the merchant wants to use. * Wire method the merchant wants to use.
@ -445,7 +444,7 @@ export interface ContractTerms {
/** /**
* Deadline to pay for the contract. * Deadline to pay for the contract.
*/ */
pay_deadline: Timestamp; pay_deadline: TalerProtocolTimestamp;
/** /**
* Maximum deposit fee covered by the merchant. * Maximum deposit fee covered by the merchant.
@ -466,7 +465,7 @@ export interface ContractTerms {
* Time indicating when the order should be delivered. * Time indicating when the order should be delivered.
* May be overwritten by individual products. * May be overwritten by individual products.
*/ */
delivery_date?: Timestamp; delivery_date?: TalerProtocolTimestamp;
/** /**
* Delivery location for (all!) products. * Delivery location for (all!) products.
@ -486,17 +485,17 @@ export interface ContractTerms {
/** /**
* Deadline for refunds. * Deadline for refunds.
*/ */
refund_deadline: Timestamp; refund_deadline: TalerProtocolTimestamp;
/** /**
* Deadline for the wire transfer. * Deadline for the wire transfer.
*/ */
wire_transfer_deadline: Timestamp; wire_transfer_deadline: TalerProtocolTimestamp;
/** /**
* Time when the contract was generated by the merchant. * Time when the contract was generated by the merchant.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
/** /**
* Order id to uniquely identify the purchase within * 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. * Structure of one exchange signing key in the /keys response.
*/ */
export class ExchangeSignKeyJson { export class ExchangeSignKeyJson {
stamp_start: Timestamp; stamp_start: TalerProtocolTimestamp;
stamp_expire: Timestamp; stamp_expire: TalerProtocolTimestamp;
stamp_end: Timestamp; stamp_end: TalerProtocolTimestamp;
key: EddsaPublicKeyString; key: EddsaPublicKeyString;
master_sig: EddsaSignatureString; master_sig: EddsaSignatureString;
} }
@ -729,7 +728,7 @@ export class ExchangeKeysJson {
/** /**
* Timestamp when this response was issued. * Timestamp when this response was issued.
*/ */
list_issue_date: Timestamp; list_issue_date: TalerProtocolTimestamp;
/** /**
* List of revoked denominations. * List of revoked denominations.
@ -747,7 +746,7 @@ export class ExchangeKeysJson {
*/ */
version: string; version: string;
reserve_closing_delay: Duration; reserve_closing_delay: TalerProtocolDuration;
} }
/** /**
@ -774,12 +773,12 @@ export class WireFeesJson {
/** /**
* Date from which the fee applies. * Date from which the fee applies.
*/ */
start_date: Timestamp; start_date: TalerProtocolTimestamp;
/** /**
* Data after which the fee doesn't apply anymore. * Data after which the fee doesn't apply anymore.
*/ */
end_date: Timestamp; end_date: TalerProtocolTimestamp;
} }
export interface AccountInfo { export interface AccountInfo {
@ -850,7 +849,7 @@ export class TipPickupGetResponse {
exchange_url: string; exchange_url: string;
expiration: Timestamp; expiration: TalerProtocolTimestamp;
} }
export enum DenomKeyType { export enum DenomKeyType {
@ -1067,7 +1066,7 @@ export interface MerchantCoinRefundSuccessStatus {
// to the customer. // to the customer.
refund_amount: AmountString; refund_amount: AmountString;
execution_time: Timestamp; execution_time: TalerProtocolTimestamp;
} }
export interface MerchantCoinRefundFailureStatus { export interface MerchantCoinRefundFailureStatus {
@ -1092,7 +1091,7 @@ export interface MerchantCoinRefundFailureStatus {
// to the customer. // to the customer.
refund_amount: AmountString; refund_amount: AmountString;
execution_time: Timestamp; execution_time: TalerProtocolTimestamp;
} }
export interface MerchantOrderStatusUnpaid { export interface MerchantOrderStatusUnpaid {
@ -1733,7 +1732,7 @@ export interface DepositSuccess {
transaction_base_url?: string; transaction_base_url?: string;
// timestamp when the deposit was received by the exchange. // timestamp when the deposit was received by the exchange.
exchange_timestamp: Timestamp; exchange_timestamp: TalerProtocolTimestamp;
// the EdDSA signature of TALER_DepositConfirmationPS using a current // the EdDSA signature of TALER_DepositConfirmationPS using a current
// signing key of the exchange affirming the successful // signing key of the exchange affirming the successful

View File

@ -23,13 +23,41 @@
*/ */
import { Codec, renderContext, Context } from "./codec.js"; import { Codec, renderContext, Context } from "./codec.js";
export interface Timestamp { export interface AbsoluteTime {
/** /**
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
readonly t_ms: number | "never"; 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 { export interface Duration {
/** /**
* Duration in milliseconds. * Duration in milliseconds.
@ -37,40 +65,32 @@ export interface Duration {
readonly d_ms: number | "forever"; readonly d_ms: number | "forever";
} }
export interface TalerProtocolDuration {
readonly d_us: number | "forever";
}
let timeshift = 0; let timeshift = 0;
export function setDangerousTimetravel(dt: number): void { export function setDangerousTimetravel(dt: number): void {
timeshift = dt; 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 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 { export function toIntegerYears(d: Duration): number {
if (typeof d.d_ms !== "number") { if (typeof d.d_ms !== "number") {
throw Error("infinite duration"); throw Error("infinite duration");
@ -81,33 +101,152 @@ export namespace Duration {
export function getForever(): Duration { export function getForever(): Duration {
return { d_ms: "forever" }; 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 namespace AbsoluteTime {
export const now = getTimestampNow; export function now(): AbsoluteTime {
export const min = timestampMin; return {
export const isExpired = isTimestampExpired; t_ms: new Date().getTime() + timeshift,
export const truncateToSecond = timestampTruncateToSecond; };
} }
export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp { export function never(): AbsoluteTime {
if (t1.t_ms === "never") { return {
return { t_ms: t2.t_ms }; t_ms: "never",
};
} }
if (t2.t_ms === "never") {
return { t_ms: t2.t_ms };
}
return { t_ms: Math.min(t1.t_ms, t2.t_ms) };
}
export function timestampMax(t1: Timestamp, t2: Timestamp): Timestamp { export function cmp(t1: AbsoluteTime, t2: AbsoluteTime): number {
if (t1.t_ms === "never") { if (t1.t_ms === "never") {
return { 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;
} }
if (t2.t_ms === "never") {
return { t_ms: "never" }; 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) };
}
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) };
}
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; const SECONDS = 1000;
@ -135,19 +274,6 @@ export function durationFromSpec(spec: {
return { d_ms }; 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 { export function durationMin(d1: Duration, d2: Duration): Duration {
if (d1.d_ms === "forever") { if (d1.d_ms === "forever") {
return { d_ms: d2.d_ms }; 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 }; return { d_ms: d1.d_ms + d2.d_ms };
} }
export function timestampCmp(t1: Timestamp, t2: Timestamp): number { export const codecForTimestamp: Codec<TalerProtocolTimestamp> = {
if (t1.t_ms === "never") { decode(x: any, c?: Context): TalerProtocolTimestamp {
if (t2.t_ms === "never") { const t_s = x.t_s;
return 0; if (typeof t_s === "string") {
} if (t_s === "never") {
return 1; return { t_s: "never" };
}
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" };
} }
throw Error(`expected timestamp at ${renderContext(c)}`); throw Error(`expected timestamp at ${renderContext(c)}`);
} }
if (typeof t_ms === "number") { if (typeof t_s === "number") {
return { t_ms }; return { t_s };
} }
throw Error(`expected timestamp at ${renderContext(c)}`); throw Error(`expected timestamp at ${renderContext(c)}`);
}, },
}; };
export const codecForDuration: Codec<Duration> = { export const codecForDuration: Codec<TalerProtocolDuration> = {
decode(x: any, c?: Context): Duration { decode(x: any, c?: Context): TalerProtocolDuration {
const d_ms = x.d_ms; const d_us = x.d_us;
if (typeof d_ms === "string") { if (typeof d_us === "string") {
if (d_ms === "forever") { if (d_us === "forever") {
return { d_ms: "forever" }; return { d_us: "forever" };
} }
throw Error(`expected duration at ${renderContext(c)}`); throw Error(`expected duration at ${renderContext(c)}`);
} }
if (typeof d_ms === "number") { if (typeof d_us === "number") {
return { d_ms }; return { d_us };
} }
throw Error(`expected duration at ${renderContext(c)}`); throw Error(`expected duration at ${renderContext(c)}`);
}, },

View File

@ -24,7 +24,7 @@
/** /**
* Imports. * Imports.
*/ */
import { Timestamp } from "./time.js"; import { TalerProtocolTimestamp } from "./time.js";
import { import {
AmountString, AmountString,
Product, Product,
@ -73,7 +73,7 @@ export interface TransactionCommon {
type: TransactionType; type: TransactionType;
// main timestamp of the transaction // main timestamp of the transaction
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
// true if the transaction is still pending, false otherwise // true if the transaction is still pending, false otherwise
// If a transaction is not longer pending, its timestamp will be updated, // If a transaction is not longer pending, its timestamp will be updated,

View File

@ -29,13 +29,13 @@ test("contract terms validation", (t) => {
merchant_pub: "12345", merchant_pub: "12345",
merchant: { name: "Foo" }, merchant: { name: "Foo" },
order_id: "test_order", order_id: "test_order",
pay_deadline: { t_ms: 42 }, pay_deadline: { t_s: 42 },
wire_transfer_deadline: { t_ms: 42 }, wire_transfer_deadline: { t_s: 42 },
merchant_base_url: "https://example.com/pay", merchant_base_url: "https://example.com/pay",
products: [], products: [],
refund_deadline: { t_ms: 42 }, refund_deadline: { t_s: 42 },
summary: "hello", summary: "hello",
timestamp: { t_ms: 42 }, timestamp: { t_s: 42 },
wire_method: "test", wire_method: "test",
}; };
@ -71,13 +71,13 @@ test("contract terms validation (locations)", (t) => {
}, },
}, },
order_id: "test_order", order_id: "test_order",
pay_deadline: { t_ms: 42 }, pay_deadline: { t_s: 42 },
wire_transfer_deadline: { t_ms: 42 }, wire_transfer_deadline: { t_s: 42 },
merchant_base_url: "https://example.com/pay", merchant_base_url: "https://example.com/pay",
products: [], products: [],
refund_deadline: { t_ms: 42 }, refund_deadline: { t_s: 42 },
summary: "hello", summary: "hello",
timestamp: { t_ms: 42 }, timestamp: { t_s: 42 },
wire_method: "test", wire_method: "test",
delivery_location: { delivery_location: {
country: "FR", country: "FR",

View File

@ -32,7 +32,11 @@ import {
codecForAmountJson, codecForAmountJson,
codecForAmountString, codecForAmountString,
} from "./amounts.js"; } from "./amounts.js";
import { Timestamp, codecForTimestamp } from "./time.js"; import {
AbsoluteTime,
codecForTimestamp,
TalerProtocolTimestamp,
} from "./time.js";
import { import {
buildCodecForObject, buildCodecForObject,
codecForString, codecForString,
@ -299,7 +303,7 @@ export interface PrepareTipResult {
* Time when the tip will expire. After it expired, it can't be picked * Time when the tip will expire. After it expired, it can't be picked
* up anymore. * up anymore.
*/ */
expirationTimestamp: Timestamp; expirationTimestamp: TalerProtocolTimestamp;
} }
export const codecForPrepareTipResult = (): Codec<PrepareTipResult> => export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
@ -460,7 +464,7 @@ export interface TalerErrorDetails {
/** /**
* Minimal information needed about a planchet for unblinding a signature. * Minimal information needed about a planchet for unblinding a signature.
* *
* Can be a withdrawal/tipping/refresh planchet. * Can be a withdrawal/tipping/refresh planchet.
*/ */
export interface PlanchetUnblindInfo { export interface PlanchetUnblindInfo {
@ -527,8 +531,8 @@ export interface DepositInfo {
coinPub: string; coinPub: string;
coinPriv: string; coinPriv: string;
spendAmount: AmountJson; spendAmount: AmountJson;
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
refundDeadline: Timestamp; refundDeadline: TalerProtocolTimestamp;
merchantPub: string; merchantPub: string;
feeDeposit: AmountJson; feeDeposit: AmountJson;
wireInfoHash: string; wireInfoHash: string;

View File

@ -478,7 +478,7 @@ class BankServiceBase {
protected globalTestState: GlobalTestState, protected globalTestState: GlobalTestState,
protected bankConfig: BankConfig, protected bankConfig: BankConfig,
protected configFile: string, protected configFile: string,
) { } ) {}
} }
/** /**
@ -920,7 +920,7 @@ export class FakeBankService {
private globalTestState: GlobalTestState, private globalTestState: GlobalTestState,
private bankConfig: FakeBankConfig, private bankConfig: FakeBankConfig,
private configFile: string, private configFile: string,
) { } ) {}
async start(): Promise<void> { async start(): Promise<void> {
this.proc = this.globalTestState.spawnService( this.proc = this.globalTestState.spawnService(
@ -1175,7 +1175,7 @@ export class ExchangeService implements ExchangeServiceInterface {
private exchangeConfig: ExchangeConfig, private exchangeConfig: ExchangeConfig,
private configFilename: string, private configFilename: string,
private keyPair: EddsaKeyPair, private keyPair: EddsaKeyPair,
) { } ) {}
get name() { get name() {
return this.exchangeConfig.name; return this.exchangeConfig.name;
@ -1276,7 +1276,7 @@ export class ExchangeService implements ExchangeServiceInterface {
accTargetType, accTargetType,
`${this.exchangeConfig.currency}:0.01`, `${this.exchangeConfig.currency}:0.01`,
`${this.exchangeConfig.currency}:0.01`, `${this.exchangeConfig.currency}:0.01`,
// `${this.exchangeConfig.currency}:0.01`, `${this.exchangeConfig.currency}:0.01`,
"upload", "upload",
], ],
); );
@ -1398,7 +1398,7 @@ export class MerchantApiClient {
constructor( constructor(
private baseUrl: string, private baseUrl: string,
public readonly auth: MerchantAuthConfiguration, public readonly auth: MerchantAuthConfiguration,
) { } ) {}
async changeAuth(auth: MerchantAuthConfiguration): Promise<void> { async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
const url = new URL("private/auth", this.baseUrl); const url = new URL("private/auth", this.baseUrl);
@ -1591,7 +1591,7 @@ export class MerchantService implements MerchantServiceInterface {
private globalState: GlobalTestState, private globalState: GlobalTestState,
private merchantConfig: MerchantConfig, private merchantConfig: MerchantConfig,
private configFilename: string, private configFilename: string,
) { } ) {}
private currentTimetravel: Duration | undefined; private currentTimetravel: Duration | undefined;
@ -1888,8 +1888,10 @@ export class WalletCli {
const resp = await sh( const resp = await sh(
self.globalTestState, self.globalTestState,
`wallet-${self.name}`, `wallet-${self.name}`,
`taler-wallet-cli ${self.timetravelArg ?? "" `taler-wallet-cli ${
} --no-throttle -LTRACE --wallet-db '${self.dbfile self.timetravelArg ?? ""
} --no-throttle -LTRACE --wallet-db '${
self.dbfile
}' api '${op}' ${shellWrap(JSON.stringify(payload))}`, }' api '${op}' ${shellWrap(JSON.stringify(payload))}`,
); );
console.log("--- wallet core response ---"); console.log("--- wallet core response ---");

View File

@ -38,7 +38,7 @@ import {
codecForAny, codecForAny,
buildCodecForUnion, buildCodecForUnion,
AmountString, AmountString,
Timestamp, AbsoluteTime,
CoinPublicKeyString, CoinPublicKeyString,
EddsaPublicKeyString, EddsaPublicKeyString,
codecForAmountString, codecForAmountString,
@ -195,7 +195,7 @@ export interface RefundDetails {
reason: string; reason: string;
// when was the refund approved // when was the refund approved
timestamp: Timestamp; timestamp: AbsoluteTime;
// Total amount that was refunded (minus a refund fee). // Total amount that was refunded (minus a refund fee).
amount: AmountString; amount: AmountString;
@ -209,7 +209,7 @@ export interface TransactionWireTransfer {
wtid: string; wtid: string;
// execution time of the wire transfer // execution time of the wire transfer
execution_time: Timestamp; execution_time: AbsoluteTime;
// Total amount that has been wire transferred // Total amount that has been wire transferred
// to the merchant // to the merchant
@ -247,10 +247,10 @@ export interface ReserveStatusEntry {
reserve_pub: string; reserve_pub: string;
// Timestamp when it was established // Timestamp when it was established
creation_time: Timestamp; creation_time: AbsoluteTime;
// Timestamp when it expires // Timestamp when it expires
expiration_time: Timestamp; expiration_time: AbsoluteTime;
// Initial amount as per reserve creation call // Initial amount as per reserve creation call
merchant_initial_amount: AmountString; merchant_initial_amount: AmountString;
@ -281,7 +281,7 @@ export interface TipCreateConfirmation {
tip_status_url: string; tip_status_url: string;
// when does the tip expire // when does the tip expire
tip_expiration: Timestamp; tip_expiration: AbsoluteTime;
} }
export interface TipCreateRequest { export interface TipCreateRequest {

View File

@ -32,6 +32,7 @@ import {
BankApi, BankApi,
BankAccessApi, BankAccessApi,
CreditDebitIndicator, CreditDebitIndicator,
OperationFailedError,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
/** /**
@ -106,8 +107,7 @@ export async function runBankApiTest(t: GlobalTestState) {
const e = await t.assertThrowsAsync(async () => { const e = await t.assertThrowsAsync(async () => {
await BankApi.registerAccount(bank, "user1", "pw1"); await BankApi.registerAccount(bank, "user1", "pw1");
}); });
t.assertAxiosError(e); t.assertTrue(e.details.httpStatusCode === 409);
t.assertTrue(e.response?.status === 409);
} }
let balResp = await BankAccessApi.getAccountBalance(bank, bankUser); let balResp = await BankAccessApi.getAccountBalance(bank, bankUser);

View File

@ -18,11 +18,11 @@
* Imports. * Imports.
*/ */
import { import {
AbsoluteTime,
codecForExchangeKeysJson, codecForExchangeKeysJson,
DenominationPubKey, DenominationPubKey,
Duration, Duration,
durationFromSpec, durationFromSpec,
stringifyTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
NodeHttpLib, NodeHttpLib,
@ -174,27 +174,37 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
const denomPubs1 = keys1.denoms.map((x) => { const denomPubs1 = keys1.denoms.map((x) => {
return { return {
denomPub: x.denom_pub, 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) => { const denomPubs2 = keys2.denoms.map((x) => {
return { return {
denomPub: x.denom_pub, 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)); const dps2 = new Set(denomPubs2.map((x) => x.denomPub));
console.log("=== KEYS RESPONSE 1 ==="); 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("num denoms", keys1.denoms.length);
console.log("denoms", JSON.stringify(denomPubs1, undefined, 2)); console.log("denoms", JSON.stringify(denomPubs1, undefined, 2));
console.log("=== KEYS RESPONSE 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("num denoms", keys2.denoms.length);
console.log("denoms", JSON.stringify(denomPubs2, undefined, 2)); 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`, `denomination with public key ${da.denomPub} is not present in new /keys response`,
); );
console.log( console.log(
`the new /keys response was issued ${stringifyTimestamp( `the new /keys response was issued ${AbsoluteTime.stringify(
keys2.list_issue_date, AbsoluteTime.fromTimestamp(keys2.list_issue_date),
)}`, )}`,
); );
console.log( console.log(

View File

@ -18,9 +18,8 @@
* Imports. * Imports.
*/ */
import { import {
AbsoluteTime,
ContractTerms, ContractTerms,
getTimestampNow,
timestampTruncateToSecond,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
WalletApiOperation, WalletApiOperation,
@ -277,7 +276,7 @@ export async function runLibeufinBasicTest(t: GlobalTestState) {
summary: "Buy me!", summary: "Buy me!",
amount: "EUR:5", amount: "EUR:5",
fulfillment_url: "taler://fulfillment-success/thx", fulfillment_url: "taler://fulfillment-success/thx",
wire_transfer_deadline: timestampTruncateToSecond(getTimestampNow()), wire_transfer_deadline: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
}; };
await makeTestPayment(t, { wallet, merchant, order }); await makeTestPayment(t, { wallet, merchant, order });

View File

@ -25,7 +25,7 @@ import {
MerchantApiClient, MerchantApiClient,
MerchantService, MerchantService,
setupDb, setupDb,
getPayto getPayto,
} from "../harness/harness.js"; } from "../harness/harness.js";
/** /**

View File

@ -18,7 +18,10 @@
* Imports. * Imports.
*/ */
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; 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 { durationFromSpec } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; 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) { export async function runRefundAutoTest(t: GlobalTestState) {
// Set up test environment // Set up test environment
const { const { wallet, bank, exchange, merchant } =
wallet, await createSimpleTestkudosEnvironment(t);
bank,
exchange,
merchant,
} = await createSimpleTestkudosEnvironment(t);
// Withdraw digital cash into the wallet. // Withdraw digital cash into the wallet.
@ -46,7 +45,7 @@ export async function runRefundAutoTest(t: GlobalTestState) {
amount: "TESTKUDOS:5", amount: "TESTKUDOS:5",
fulfillment_url: "taler://fulfillment-success/thx", fulfillment_url: "taler://fulfillment-success/thx",
auto_refund: { auto_refund: {
d_ms: 3000, d_us: 3000 * 1000,
}, },
}, },
refund_delay: durationFromSpec({ minutes: 5 }), refund_delay: durationFromSpec({ minutes: 5 }),

View File

@ -24,10 +24,8 @@ import {
applyTimeTravel, applyTimeTravel,
} from "../harness/helpers.js"; } from "../harness/helpers.js";
import { import {
AbsoluteTime,
durationFromSpec, durationFromSpec,
timestampAddDuration,
getTimestampNow,
timestampTruncateToSecond,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@ -55,9 +53,9 @@ export async function runRefundGoneTest(t: GlobalTestState) {
summary: "Buy me!", summary: "Buy me!",
amount: "TESTKUDOS:5", amount: "TESTKUDOS:5",
fulfillment_url: "taler://fulfillment-success/thx", fulfillment_url: "taler://fulfillment-success/thx",
pay_deadline: timestampTruncateToSecond( pay_deadline: AbsoluteTime.toTimestamp(
timestampAddDuration( AbsoluteTime.addDuration(
getTimestampNow(), AbsoluteTime.now(),
durationFromSpec({ durationFromSpec({
minutes: 10, minutes: 10,
}), }),

View File

@ -25,6 +25,7 @@ import {
AmountString, AmountString,
buildCodecForObject, buildCodecForObject,
Codec, Codec,
codecForAny,
codecForString, codecForString,
encodeCrock, encodeCrock,
getRandomBytes, getRandomBytes,
@ -102,15 +103,16 @@ export namespace BankApi {
const resp = await bank.http.postJson(url.href, { username, password }); const resp = await bank.http.postJson(url.href, { username, password });
let paytoUri = `payto://x-taler-bank/localhost/${username}`; let paytoUri = `payto://x-taler-bank/localhost/${username}`;
if (resp.status !== 200 && resp.status !== 202) { if (resp.status !== 200 && resp.status !== 202) {
logger.error(`${j2s(await resp.json())}`) logger.error(`${j2s(await resp.json())}`);
throw new Error(); throw new Error();
} }
const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
// LibEuFin demobank returns payto URI in response
if (respJson.paytoUri) {
paytoUri = respJson.paytoUri;
}
try { try {
const respJson = await resp.json(); const respJson = await resp.json();
// LibEuFin demobank returns payto URI in response
if (respJson.paytoUri) {
paytoUri = respJson.paytoUri;
}
} catch (e) {} } catch (e) {}
return { return {
password, password,

View File

@ -34,7 +34,8 @@ import {
BalancesResponse, BalancesResponse,
AmountJson, AmountJson,
DenominationPubKey, DenominationPubKey,
Timestamp, AbsoluteTime,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { CryptoApi } from "./crypto/workers/cryptoApi.js"; import { CryptoApi } from "./crypto/workers/cryptoApi.js";
import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js"; import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js";
@ -165,22 +166,22 @@ export interface DenomInfo {
/** /**
* Validity start date of the denomination. * Validity start date of the denomination.
*/ */
stampStart: Timestamp; stampStart: TalerProtocolTimestamp;
/** /**
* Date after which the currency can't be withdrawn anymore. * Date after which the currency can't be withdrawn anymore.
*/ */
stampExpireWithdraw: Timestamp; stampExpireWithdraw: TalerProtocolTimestamp;
/** /**
* Date after the denomination officially doesn't exist anymore. * 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. * Data after which coins of this denomination can't be deposited anymore.
*/ */
stampExpireDeposit: Timestamp; stampExpireDeposit: TalerProtocolTimestamp;
} }
export type NotificationListener = (n: WalletNotification) => void; export type NotificationListener = (n: WalletNotification) => void;

View File

@ -207,7 +207,7 @@ export class CryptoApi {
} }
}; };
ws.terminationTimerHandle = timer.after(15 * 1000, destroy); ws.terminationTimerHandle = timer.after(15 * 1000, destroy);
ws.terminationTimerHandle.unref(); //ws.terminationTimerHandle.unref();
} }
handleWorkerError(ws: WorkerState, e: any): void { handleWorkerError(ws: WorkerState, e: any): void {

View File

@ -67,13 +67,11 @@ import {
setupWithdrawPlanchet, setupWithdrawPlanchet,
stringToBytes, stringToBytes,
TalerSignaturePurpose, TalerSignaturePurpose,
Timestamp, AbsoluteTime,
timestampTruncateToSecond,
typedArrayConcat,
BlindedDenominationSignature, BlindedDenominationSignature,
RsaUnblindedSignature,
UnblindedSignature, UnblindedSignature,
PlanchetUnblindInfo, PlanchetUnblindInfo,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import bigint from "big-integer"; import bigint from "big-integer";
import { DenominationRecord, WireFee } from "../../db.js"; import { DenominationRecord, WireFee } from "../../db.js";
@ -110,18 +108,16 @@ function amountToBuffer(amount: AmountJson): Uint8Array {
return u8buf; return u8buf;
} }
function timestampRoundedToBuffer(ts: Timestamp): Uint8Array { function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array {
const b = new ArrayBuffer(8); const b = new ArrayBuffer(8);
const v = new DataView(b); const v = new DataView(b);
const tsRounded = timestampTruncateToSecond(ts); // The buffer we sign over represents the timestamp in microseconds.
if (typeof v.setBigUint64 !== "undefined") { 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); v.setBigUint64(0, s);
} else { } else {
const s = const s =
tsRounded.t_ms === "never" ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000);
? bigint.zero
: bigint(tsRounded.t_ms).times(1000);
const arr = s.toArray(2 ** 8).value; const arr = s.toArray(2 ** 8).value;
let offset = 8 - arr.length; let offset = 8 - arr.length;
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {

View File

@ -36,9 +36,10 @@ import {
Product, Product,
RefreshReason, RefreshReason,
TalerErrorDetails, TalerErrorDetails,
Timestamp,
UnblindedSignature, UnblindedSignature,
CoinEnvelope, CoinEnvelope,
TalerProtocolTimestamp,
TalerProtocolDuration,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { RetryInfo } from "./util/retries.js"; import { RetryInfo } from "./util/retries.js";
import { PayCoinSelection } from "./util/coinSelection.js"; import { PayCoinSelection } from "./util/coinSelection.js";
@ -152,7 +153,7 @@ export interface ReserveRecord {
/** /**
* Time when the reserve was created. * Time when the reserve was created.
*/ */
timestampCreated: Timestamp; timestampCreated: TalerProtocolTimestamp;
/** /**
* Time when the information about this reserve was posted to the bank. * 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. * Set to undefined if that hasn't happened yet.
*/ */
timestampReserveInfoPosted: Timestamp | undefined; timestampReserveInfoPosted: TalerProtocolTimestamp | undefined;
/** /**
* Time when the reserve was confirmed by the bank. * Time when the reserve was confirmed by the bank.
* *
* Set to undefined if not confirmed yet. * Set to undefined if not confirmed yet.
*/ */
timestampBankConfirmed: Timestamp | undefined; timestampBankConfirmed: TalerProtocolTimestamp | undefined;
/** /**
* Wire information (as payto URI) for the bank account that * Wire information (as payto URI) for the bank account that
@ -217,11 +218,6 @@ export interface ReserveRecord {
*/ */
operationStatus: OperationStatus; operationStatus: OperationStatus;
/**
* Time of the last successful status query.
*/
lastSuccessfulStatusQuery: Timestamp | undefined;
/** /**
* Retry info, in case the reserve needs to be processed again * Retry info, in case the reserve needs to be processed again
* later, either due to an error or because the wallet needs to * later, either due to an error or because the wallet needs to
@ -350,22 +346,22 @@ export interface DenominationRecord {
/** /**
* Validity start date of the denomination. * Validity start date of the denomination.
*/ */
stampStart: Timestamp; stampStart: TalerProtocolTimestamp;
/** /**
* Date after which the currency can't be withdrawn anymore. * Date after which the currency can't be withdrawn anymore.
*/ */
stampExpireWithdraw: Timestamp; stampExpireWithdraw: TalerProtocolTimestamp;
/** /**
* Date after the denomination officially doesn't exist anymore. * 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. * 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 * 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 * Latest list issue date of the "/keys" response
* that includes this denomination. * that includes this denomination.
*/ */
listIssueDate: Timestamp; listIssueDate: TalerProtocolTimestamp;
} }
/** /**
@ -441,7 +437,7 @@ export interface ExchangeDetailsRecord {
*/ */
protocolVersion: string; protocolVersion: string;
reserveClosingDelay: Duration; reserveClosingDelay: TalerProtocolDuration;
/** /**
* Signing keys we got from the exchange, can also contain * Signing keys we got from the exchange, can also contain
@ -478,7 +474,7 @@ export interface ExchangeDetailsRecord {
* *
* Used during backup merging. * Used during backup merging.
*/ */
termsOfServiceAcceptedTimestamp: Timestamp | undefined; termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
wireInfo: WireInfo; wireInfo: WireInfo;
} }
@ -503,7 +499,7 @@ export interface ExchangeDetailsPointer {
* Timestamp when the (masterPublicKey, currency) pointer * Timestamp when the (masterPublicKey, currency) pointer
* has been updated. * has been updated.
*/ */
updateClock: Timestamp; updateClock: TalerProtocolTimestamp;
} }
/** /**
@ -528,14 +524,14 @@ export interface ExchangeRecord {
/** /**
* Last time when the exchange was updated. * Last time when the exchange was updated.
*/ */
lastUpdate: Timestamp | undefined; lastUpdate: TalerProtocolTimestamp | undefined;
/** /**
* Next scheduled update for the exchange. * Next scheduled update for the exchange.
* *
* (This field must always be present, so we can index on the timestamp.) * (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. * Next time that we should check if coins need to be refreshed.
@ -543,7 +539,7 @@ export interface ExchangeRecord {
* Updated whenever the exchange's denominations are updated or when * Updated whenever the exchange's denominations are updated or when
* the refresh check has been done. * the refresh check has been done.
*/ */
nextRefreshCheck: Timestamp; nextRefreshCheck: TalerProtocolTimestamp;
/** /**
* Last error (if any) for fetching updated information about the * Last error (if any) for fetching updated information about the
@ -793,7 +789,7 @@ export interface ProposalRecord {
* Timestamp (in ms) of when the record * Timestamp (in ms) of when the record
* was created. * was created.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
/** /**
* Private key for the nonce. * Private key for the nonce.
@ -837,7 +833,7 @@ export interface TipRecord {
* Has the user accepted the tip? Only after the tip has been accepted coins * Has the user accepted the tip? Only after the tip has been accepted coins
* withdrawn from the tip may be used. * withdrawn from the tip may be used.
*/ */
acceptedTimestamp: Timestamp | undefined; acceptedTimestamp: TalerProtocolTimestamp | undefined;
/** /**
* The tipped amount. * The tipped amount.
@ -849,7 +845,7 @@ export interface TipRecord {
/** /**
* Timestamp, the tip can't be picked up anymore after this deadline. * 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. * The exchange that will sign our coins, chosen by the merchant.
@ -884,13 +880,13 @@ export interface TipRecord {
*/ */
merchantTipId: string; merchantTipId: string;
createdTimestamp: Timestamp; createdTimestamp: TalerProtocolTimestamp;
/** /**
* Timestamp for when the wallet finished picking up the tip * Timestamp for when the wallet finished picking up the tip
* from the merchant. * from the merchant.
*/ */
pickedUpTimestamp: Timestamp | undefined; pickedUpTimestamp: TalerProtocolTimestamp | undefined;
/** /**
* Retry info, even present when the operation isn't active to allow indexing * Retry info, even present when the operation isn't active to allow indexing
@ -959,12 +955,12 @@ export interface RefreshGroupRecord {
*/ */
statusPerCoin: RefreshCoinStatus[]; statusPerCoin: RefreshCoinStatus[];
timestampCreated: Timestamp; timestampCreated: TalerProtocolTimestamp;
/** /**
* Timestamp when the refresh session finished. * Timestamp when the refresh session finished.
*/ */
timestampFinished: Timestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
/** /**
* No coins are pending, but at least one is frozen. * No coins are pending, but at least one is frozen.
@ -1025,12 +1021,12 @@ export interface WireFee {
/** /**
* Start date of the fee. * Start date of the fee.
*/ */
startStamp: Timestamp; startStamp: TalerProtocolTimestamp;
/** /**
* End date of the fee. * End date of the fee.
*/ */
endStamp: Timestamp; endStamp: TalerProtocolTimestamp;
/** /**
* Signature made by the exchange master key. * Signature made by the exchange master key.
@ -1054,12 +1050,12 @@ export type WalletRefundItem =
export interface WalletRefundItemCommon { export interface WalletRefundItemCommon {
// Execution time as claimed by the merchant // Execution time as claimed by the merchant
executionTime: Timestamp; executionTime: TalerProtocolTimestamp;
/** /**
* Time when the wallet became aware of the refund. * Time when the wallet became aware of the refund.
*/ */
obtainedTime: Timestamp; obtainedTime: TalerProtocolTimestamp;
refundAmount: AmountJson; refundAmount: AmountJson;
@ -1141,14 +1137,14 @@ export interface WalletContractData {
orderId: string; orderId: string;
merchantBaseUrl: string; merchantBaseUrl: string;
summary: string; summary: string;
autoRefund: Duration | undefined; autoRefund: TalerProtocolDuration | undefined;
maxWireFee: AmountJson; maxWireFee: AmountJson;
wireFeeAmortization: number; wireFeeAmortization: number;
payDeadline: Timestamp; payDeadline: TalerProtocolTimestamp;
refundDeadline: Timestamp; refundDeadline: TalerProtocolTimestamp;
allowedAuditors: AllowedAuditorInfo[]; allowedAuditors: AllowedAuditorInfo[];
allowedExchanges: AllowedExchangeInfo[]; allowedExchanges: AllowedExchangeInfo[];
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
wireMethod: string; wireMethod: string;
wireInfoHash: string; wireInfoHash: string;
maxDepositFee: AmountJson; maxDepositFee: AmountJson;
@ -1215,8 +1211,10 @@ export interface PurchaseRecord {
/** /**
* Timestamp of the first time that sending a payment to the merchant * Timestamp of the first time that sending a payment to the merchant
* for this purchase was successful. * 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; merchantPaySig: string | undefined;
@ -1224,7 +1222,7 @@ export interface PurchaseRecord {
* When was the purchase made? * When was the purchase made?
* Refers to the time that the user accepted. * Refers to the time that the user accepted.
*/ */
timestampAccept: Timestamp; timestampAccept: TalerProtocolTimestamp;
/** /**
* Pending refunds for the purchase. A refund is pending * Pending refunds for the purchase. A refund is pending
@ -1236,7 +1234,7 @@ export interface PurchaseRecord {
* When was the last refund made? * When was the last refund made?
* Set to 0 if no refund was made on the purchase. * 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). * Last session signature that we submitted to /pay (if any).
@ -1273,7 +1271,7 @@ export interface PurchaseRecord {
/** /**
* Continue querying the refund status until this deadline has expired. * 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 * Is the payment frozen? I.e. did we encounter
@ -1308,12 +1306,12 @@ export interface WalletBackupConfState {
/** /**
* Timestamp stored in the last backup. * Timestamp stored in the last backup.
*/ */
lastBackupTimestamp?: Timestamp; lastBackupTimestamp?: TalerProtocolTimestamp;
/** /**
* Last time we tried to do a backup. * Last time we tried to do a backup.
*/ */
lastBackupCheckTimestamp?: Timestamp; lastBackupCheckTimestamp?: TalerProtocolTimestamp;
lastBackupNonce?: string; lastBackupNonce?: string;
} }
@ -1362,14 +1360,14 @@ export interface WithdrawalGroupRecord {
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
timestampStart: Timestamp; timestampStart: TalerProtocolTimestamp;
/** /**
* When was the withdrawal operation completed? * When was the withdrawal operation completed?
* *
* FIXME: We should probably drop this and introduce an OperationStatus field. * FIXME: We should probably drop this and introduce an OperationStatus field.
*/ */
timestampFinish?: Timestamp; timestampFinish?: TalerProtocolTimestamp;
/** /**
* Operation status of the withdrawal group. * Operation status of the withdrawal group.
@ -1429,9 +1427,9 @@ export interface RecoupGroupRecord {
*/ */
recoupGroupId: string; recoupGroupId: string;
timestampStarted: Timestamp; timestampStarted: TalerProtocolTimestamp;
timestampFinished: Timestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
/** /**
* Public keys that identify the coins being recouped * Public keys that identify the coins being recouped
@ -1482,7 +1480,7 @@ export type BackupProviderState =
} }
| { | {
tag: BackupProviderStateTag.Ready; tag: BackupProviderStateTag.Ready;
nextBackupTimestamp: Timestamp; nextBackupTimestamp: TalerProtocolTimestamp;
} }
| { | {
tag: BackupProviderStateTag.Retrying; tag: BackupProviderStateTag.Retrying;
@ -1529,7 +1527,7 @@ export interface BackupProviderRecord {
* Does NOT correspond to the timestamp of the backup, * Does NOT correspond to the timestamp of the backup,
* which only changes when the backup content changes. * which only changes when the backup content changes.
*/ */
lastBackupCycleTimestamp?: Timestamp; lastBackupCycleTimestamp?: TalerProtocolTimestamp;
/** /**
* Proposal that we're currently trying to pay for. * Proposal that we're currently trying to pay for.
@ -1594,9 +1592,9 @@ export interface DepositGroupRecord {
depositedPerCoin: boolean[]; depositedPerCoin: boolean[];
timestampCreated: Timestamp; timestampCreated: TalerProtocolTimestamp;
timestampFinished: Timestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
operationStatus: OperationStatus; operationStatus: OperationStatus;
@ -1618,14 +1616,14 @@ export interface GhostDepositGroupRecord {
* When multiple deposits for the same contract terms hash * When multiple deposits for the same contract terms hash
* have a different timestamp, we choose the earliest one. * have a different timestamp, we choose the earliest one.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
contractTermsHash: string; contractTermsHash: string;
deposits: { deposits: {
coinPub: string; coinPub: string;
amount: AmountString; amount: AmountString;
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
depositFee: AmountString; depositFee: AmountString;
merchantPub: string; merchantPub: string;
coinSig: string; coinSig: string;

View File

@ -44,7 +44,7 @@ import {
hashWire, hashWire,
Logger, Logger,
parsePaytoUri, parsePaytoUri,
Timestamp, AbsoluteTime,
UnblindedSignature, UnblindedSignature,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { DenominationRecord } from "./db.js"; import { DenominationRecord } from "./db.js";
@ -222,10 +222,11 @@ export async function depositCoin(args: {
const depositPayto = const depositPayto =
args.depositPayto ?? "payto://x-taler-bank/localhost/foo"; args.depositPayto ?? "payto://x-taler-bank/localhost/foo";
const wireSalt = encodeCrock(getRandomBytes(16)); const wireSalt = encodeCrock(getRandomBytes(16));
const timestampNow = AbsoluteTime.toTimestamp(AbsoluteTime.now());
const contractTermsHash = encodeCrock(getRandomBytes(64)); const contractTermsHash = encodeCrock(getRandomBytes(64));
const depositTimestamp = Timestamp.truncateToSecond(Timestamp.now()); const depositTimestamp = timestampNow;
const refundDeadline = Timestamp.truncateToSecond(Timestamp.now()); const refundDeadline = timestampNow;
const wireTransferDeadline = Timestamp.truncateToSecond(Timestamp.now()); const wireTransferDeadline = timestampNow;
const merchantPub = encodeCrock(getRandomBytes(32)); const merchantPub = encodeCrock(getRandomBytes(32));
const dp = await cryptoApi.signDepositPermission({ const dp = await cryptoApi.signDepositPermission({
coinPriv: coin.coinPriv, coinPriv: coin.coinPriv,

View File

@ -49,14 +49,13 @@ import {
BackupWithdrawalGroup, BackupWithdrawalGroup,
canonicalizeBaseUrl, canonicalizeBaseUrl,
canonicalJson, canonicalJson,
getTimestampNow,
Logger, Logger,
timestampToIsoString,
WalletBackupContentV1, WalletBackupContentV1,
hash, hash,
encodeCrock, encodeCrock,
getRandomBytes, getRandomBytes,
stringToBytes, stringToBytes,
AbsoluteTime,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../../common.js"; import { InternalWalletState } from "../../common.js";
import { import {
@ -455,7 +454,7 @@ export async function exportBackup(
}); });
}); });
const ts = getTimestampNow(); const ts = AbsoluteTime.toTimestamp(AbsoluteTime.now());
if (!bs.lastBackupTimestamp) { if (!bs.lastBackupTimestamp) {
bs.lastBackupTimestamp = ts; bs.lastBackupTimestamp = ts;
@ -496,9 +495,9 @@ export async function exportBackup(
); );
bs.lastBackupNonce = encodeCrock(getRandomBytes(32)); bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
logger.trace( logger.trace(
`setting timestamp to ${timestampToIsoString(ts)} and nonce to ${ `setting timestamp to ${AbsoluteTime.toIsoString(
bs.lastBackupNonce AbsoluteTime.fromTimestamp(ts),
}`, )} and nonce to ${bs.lastBackupNonce}`,
); );
await tx.config.put({ await tx.config.put({
key: WALLET_BACKUP_STATE_KEY, key: WALLET_BACKUP_STATE_KEY,

View File

@ -20,7 +20,6 @@ import {
Amounts, Amounts,
BackupDenomSel, BackupDenomSel,
WalletBackupContentV1, WalletBackupContentV1,
getTimestampNow,
BackupCoinSourceType, BackupCoinSourceType,
BackupProposalStatus, BackupProposalStatus,
codecForContractTerms, codecForContractTerms,
@ -28,6 +27,8 @@ import {
RefreshReason, RefreshReason,
BackupRefreshReason, BackupRefreshReason,
DenomKeyType, DenomKeyType,
AbsoluteTime,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
WalletContractData, WalletContractData,
@ -277,8 +278,8 @@ export async function importBackup(
permanent: true, permanent: true,
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastUpdate: undefined, lastUpdate: undefined,
nextUpdate: getTimestampNow(), nextUpdate: TalerProtocolTimestamp.now(),
nextRefreshCheck: getTimestampNow(), nextRefreshCheck: TalerProtocolTimestamp.now(),
}); });
} }
@ -465,7 +466,6 @@ export async function importBackup(
senderWire: backupReserve.sender_wire, senderWire: backupReserve.sender_wire,
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
lastSuccessfulStatusQuery: { t_ms: "never" },
initialWithdrawalGroupId: initialWithdrawalGroupId:
backupReserve.initial_withdrawal_group_id, backupReserve.initial_withdrawal_group_id,
initialWithdrawalStarted: initialWithdrawalStarted:
@ -752,7 +752,7 @@ export async function importBackup(
noncePub: noncePub:
cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv], cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
lastPayError: undefined, lastPayError: undefined,
autoRefundDeadline: { t_ms: "never" }, autoRefundDeadline: TalerProtocolTimestamp.never(),
refundStatusRetryInfo: initRetryInfo(), refundStatusRetryInfo: initRetryInfo(),
lastRefundStatusError: undefined, lastRefundStatusError: undefined,
timestampAccept: backupPurchase.timestamp_accept, timestampAccept: backupPurchase.timestamp_accept,

View File

@ -40,21 +40,19 @@ import {
ConfirmPayResultType, ConfirmPayResultType,
DenomKeyType, DenomKeyType,
durationFromSpec, durationFromSpec,
getTimestampNow,
hashDenomPub, hashDenomPub,
HttpStatusCode, HttpStatusCode,
j2s, j2s,
LibtoolVersion,
Logger, Logger,
notEmpty, notEmpty,
PreparePayResultType, PreparePayResultType,
RecoveryLoadRequest, RecoveryLoadRequest,
RecoveryMergeStrategy, RecoveryMergeStrategy,
TalerErrorDetails, TalerErrorDetails,
Timestamp, AbsoluteTime,
timestampAddDuration,
URL, URL,
WalletBackupContentV1, WalletBackupContentV1,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { gunzipSync, gzipSync } from "fflate"; import { gunzipSync, gzipSync } from "fflate";
import { InternalWalletState } from "../../common.js"; import { InternalWalletState } from "../../common.js";
@ -250,11 +248,13 @@ interface BackupForProviderArgs {
retryAfterPayment: boolean; retryAfterPayment: boolean;
} }
function getNextBackupTimestamp(): Timestamp { function getNextBackupTimestamp(): TalerProtocolTimestamp {
// FIXME: Randomize! // FIXME: Randomize!
return timestampAddDuration( return AbsoluteTime.toTimestamp(
getTimestampNow(), AbsoluteTime.addDuration(
durationFromSpec({ minutes: 5 }), AbsoluteTime.now(),
durationFromSpec({ minutes: 5 }),
),
); );
} }
@ -324,7 +324,7 @@ async function runBackupCycleForProvider(
if (!prov) { if (!prov) {
return; return;
} }
prov.lastBackupCycleTimestamp = getTimestampNow(); prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
prov.state = { prov.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getNextBackupTimestamp(), nextBackupTimestamp: getNextBackupTimestamp(),
@ -404,7 +404,7 @@ async function runBackupCycleForProvider(
return; return;
} }
prov.lastBackupHash = encodeCrock(currentBackupHash); prov.lastBackupHash = encodeCrock(currentBackupHash);
prov.lastBackupCycleTimestamp = getTimestampNow(); prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
prov.state = { prov.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getNextBackupTimestamp(), nextBackupTimestamp: getNextBackupTimestamp(),
@ -641,7 +641,7 @@ export async function addBackupProvider(
if (req.activate) { if (req.activate) {
oldProv.state = { oldProv.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getTimestampNow(), nextBackupTimestamp: TalerProtocolTimestamp.now(),
}; };
logger.info("setting existing backup provider to active"); logger.info("setting existing backup provider to active");
await tx.backupProviders.put(oldProv); await tx.backupProviders.put(oldProv);
@ -662,7 +662,7 @@ export async function addBackupProvider(
if (req.activate) { if (req.activate) {
state = { state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getTimestampNow(), nextBackupTimestamp: TalerProtocolTimestamp.now(),
}; };
} else { } else {
state = { state = {
@ -701,8 +701,8 @@ export interface ProviderInfo {
* Last communication issue with the provider. * Last communication issue with the provider.
*/ */
lastError?: TalerErrorDetails; lastError?: TalerErrorDetails;
lastSuccessfulBackupTimestamp?: Timestamp; lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp;
lastAttemptedBackupTimestamp?: Timestamp; lastAttemptedBackupTimestamp?: TalerProtocolTimestamp;
paymentProposalIds: string[]; paymentProposalIds: string[];
backupProblem?: BackupProblem; backupProblem?: BackupProblem;
paymentStatus: ProviderPaymentStatus; paymentStatus: ProviderPaymentStatus;
@ -724,7 +724,7 @@ export interface BackupConflictingDeviceProblem {
type: "backup-conflicting-device"; type: "backup-conflicting-device";
otherDeviceId: string; otherDeviceId: string;
myDeviceId: string; myDeviceId: string;
backupTimestamp: Timestamp; backupTimestamp: AbsoluteTime;
} }
export type ProviderPaymentStatus = export type ProviderPaymentStatus =
@ -774,12 +774,12 @@ export interface ProviderPaymentPending {
export interface ProviderPaymentPaid { export interface ProviderPaymentPaid {
type: ProviderPaymentType.Paid; type: ProviderPaymentType.Paid;
paidUntil: Timestamp; paidUntil: AbsoluteTime;
} }
export interface ProviderPaymentTermsChanged { export interface ProviderPaymentTermsChanged {
type: ProviderPaymentType.TermsChanged; type: ProviderPaymentType.TermsChanged;
paidUntil: Timestamp; paidUntil: AbsoluteTime;
oldTerms: BackupProviderTerms; oldTerms: BackupProviderTerms;
newTerms: BackupProviderTerms; newTerms: BackupProviderTerms;
} }
@ -811,8 +811,8 @@ async function getProviderPaymentInfo(
if (status.paid) { if (status.paid) {
return { return {
type: ProviderPaymentType.Paid, type: ProviderPaymentType.Paid,
paidUntil: timestampAddDuration( paidUntil: AbsoluteTime.addDuration(
status.contractTerms.timestamp, AbsoluteTime.fromTimestamp(status.contractTerms.timestamp),
durationFromSpec({ years: 1 }), durationFromSpec({ years: 1 }),
), ),
}; };
@ -915,7 +915,7 @@ async function backupRecoveryTheirs(
paymentProposalIds: [], paymentProposalIds: [],
state: { state: {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getTimestampNow(), nextBackupTimestamp: TalerProtocolTimestamp.now(),
}, },
uids: [encodeCrock(getRandomBytes(32))], uids: [encodeCrock(getRandomBytes(32))],
}); });

View File

@ -15,6 +15,7 @@
*/ */
import { import {
AbsoluteTime,
AmountJson, AmountJson,
Amounts, Amounts,
buildCodecForObject, buildCodecForObject,
@ -27,21 +28,16 @@ import {
ContractTerms, ContractTerms,
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
DenomKeyType,
durationFromSpec, durationFromSpec,
encodeCrock, encodeCrock,
GetFeeForDepositRequest, GetFeeForDepositRequest,
getRandomBytes, getRandomBytes,
getTimestampNow,
hashWire, hashWire,
Logger, Logger,
NotificationType, NotificationType,
parsePaytoUri, parsePaytoUri,
TalerErrorDetails, TalerErrorDetails,
Timestamp, TalerProtocolTimestamp,
timestampAddDuration,
timestampIsBetween,
timestampTruncateToSecond,
TrackDepositGroupRequest, TrackDepositGroupRequest,
TrackDepositGroupResponse, TrackDepositGroupResponse,
URL, URL,
@ -212,7 +208,7 @@ async function processDepositGroupImpl(
} }
} }
if (allDeposited) { if (allDeposited) {
dg.timestampFinished = getTimestampNow(); dg.timestampFinished = TalerProtocolTimestamp.now();
dg.operationStatus = OperationStatus.Finished; dg.operationStatus = OperationStatus.Finished;
delete dg.lastError; delete dg.lastError;
delete dg.retryInfo; delete dg.retryInfo;
@ -310,13 +306,8 @@ export async function getFeeForDeposit(
} }
}); });
const timestamp = getTimestampNow(); const timestamp = AbsoluteTime.now();
const timestampRound = timestampTruncateToSecond(timestamp); const timestampRound = AbsoluteTime.toTimestamp(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 contractTerms: ContractTerms = { const contractTerms: ContractTerms = {
auditors: [], auditors: [],
exchanges: exchangeInfos, exchanges: exchangeInfos,
@ -331,15 +322,14 @@ export async function getFeeForDeposit(
wire_transfer_deadline: timestampRound, wire_transfer_deadline: timestampRound,
order_id: "", order_id: "",
h_wire: "", h_wire: "",
pay_deadline: timestampAddDuration( pay_deadline: AbsoluteTime.toTimestamp(
timestampRound, AbsoluteTime.addDuration(timestamp, durationFromSpec({ hours: 1 })),
durationFromSpec({ hours: 1 }),
), ),
merchant: { merchant: {
name: "", name: "",
}, },
merchant_pub: "", merchant_pub: "",
refund_deadline: { t_ms: 0 }, refund_deadline: TalerProtocolTimestamp.zero(),
}; };
const contractData = extractContractData(contractTerms, "", ""); const contractData = extractContractData(contractTerms, "", "");
@ -399,8 +389,8 @@ export async function createDepositGroup(
} }
}); });
const timestamp = getTimestampNow(); const now = AbsoluteTime.now();
const timestampRound = timestampTruncateToSecond(timestamp); const nowRounded = AbsoluteTime.toTimestamp(now);
const noncePair = await ws.cryptoApi.createEddsaKeypair(); const noncePair = await ws.cryptoApi.createEddsaKeypair();
const merchantPair = await ws.cryptoApi.createEddsaKeypair(); const merchantPair = await ws.cryptoApi.createEddsaKeypair();
const wireSalt = encodeCrock(getRandomBytes(16)); const wireSalt = encodeCrock(getRandomBytes(16));
@ -412,24 +402,23 @@ export async function createDepositGroup(
max_fee: Amounts.stringify(amount), max_fee: Amounts.stringify(amount),
max_wire_fee: Amounts.stringify(amount), max_wire_fee: Amounts.stringify(amount),
wire_method: p.targetType, wire_method: p.targetType,
timestamp: timestampRound, timestamp: nowRounded,
merchant_base_url: "", merchant_base_url: "",
summary: "", summary: "",
nonce: noncePair.pub, nonce: noncePair.pub,
wire_transfer_deadline: timestampRound, wire_transfer_deadline: nowRounded,
order_id: "", order_id: "",
// This is always the v2 wire hash, as we're the "merchant" and support v2. // This is always the v2 wire hash, as we're the "merchant" and support v2.
h_wire: wireHash, h_wire: wireHash,
// Required for older exchanges. // Required for older exchanges.
pay_deadline: timestampAddDuration( pay_deadline: AbsoluteTime.toTimestamp(
timestampRound, AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 })),
durationFromSpec({ hours: 1 }),
), ),
merchant: { merchant: {
name: "", name: "",
}, },
merchant_pub: merchantPair.pub, merchant_pub: merchantPair.pub,
refund_deadline: { t_ms: 0 }, refund_deadline: TalerProtocolTimestamp.zero(),
}; };
const contractTermsHash = await ws.cryptoApi.hashString( const contractTermsHash = await ws.cryptoApi.hashString(
@ -482,7 +471,7 @@ export async function createDepositGroup(
depositGroupId, depositGroupId,
noncePriv: noncePair.priv, noncePriv: noncePair.priv,
noncePub: noncePair.pub, noncePub: noncePair.pub,
timestampCreated: timestamp, timestampCreated: AbsoluteTime.toTimestamp(now),
timestampFinished: undefined, timestampFinished: undefined,
payCoinSelection: payCoinSel, payCoinSelection: payCoinSel,
payCoinSelectionUid: encodeCrock(getRandomBytes(32)), payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
@ -570,10 +559,10 @@ export async function getEffectiveDepositAmount(
// about "find method not found on undefined" when the wireType // about "find method not found on undefined" when the wireType
// is not supported by the Exchange. // is not supported by the Exchange.
const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
return timestampIsBetween( return AbsoluteTime.isBetween(
getTimestampNow(), AbsoluteTime.now(),
x.startStamp, AbsoluteTime.fromTimestamp(x.startStamp),
x.endStamp, AbsoluteTime.fromTimestamp(x.endStamp),
); );
})?.wireFee; })?.wireFee;
if (fee) { if (fee) {
@ -656,10 +645,10 @@ export async function getTotalFeeForDepositAmount(
// about "find method not found on undefined" when the wireType // about "find method not found on undefined" when the wireType
// is not supported by the Exchange. // is not supported by the Exchange.
const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
return timestampIsBetween( return AbsoluteTime.isBetween(
getTimestampNow(), AbsoluteTime.now(),
x.startStamp, AbsoluteTime.fromTimestamp(x.startStamp),
x.endStamp, AbsoluteTime.fromTimestamp(x.endStamp),
); );
})?.wireFee; })?.wireFee;
if (fee) { if (fee) {

View File

@ -28,8 +28,6 @@ import {
durationFromSpec, durationFromSpec,
ExchangeSignKeyJson, ExchangeSignKeyJson,
ExchangeWireJson, ExchangeWireJson,
getTimestampNow,
isTimestampExpired,
Logger, Logger,
NotificationType, NotificationType,
parsePaytoUri, parsePaytoUri,
@ -37,13 +35,15 @@ import {
TalerErrorCode, TalerErrorCode,
URL, URL,
TalerErrorDetails, TalerErrorDetails,
Timestamp, AbsoluteTime,
hashDenomPub, hashDenomPub,
LibtoolVersion, LibtoolVersion,
codecForAny, codecForAny,
DenominationPubKey, DenominationPubKey,
DenomKeyType, DenomKeyType,
ExchangeKeysJson, ExchangeKeysJson,
TalerProtocolTimestamp,
TalerProtocolDuration,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
import { CryptoApi } from "../crypto/workers/cryptoApi.js"; import { CryptoApi } from "../crypto/workers/cryptoApi.js";
@ -57,7 +57,7 @@ import {
WireInfo, WireInfo,
} from "../db.js"; } from "../db.js";
import { import {
getExpiryTimestamp, getExpiry,
HttpRequestLibrary, HttpRequestLibrary,
readSuccessResponseJsonOrThrow, readSuccessResponseJsonOrThrow,
readSuccessResponseTextOrThrow, readSuccessResponseTextOrThrow,
@ -80,7 +80,7 @@ const logger = new Logger("exchanges.ts");
function denominationRecordFromKeys( function denominationRecordFromKeys(
exchangeBaseUrl: string, exchangeBaseUrl: string,
exchangeMasterPub: string, exchangeMasterPub: string,
listIssueDate: Timestamp, listIssueDate: TalerProtocolTimestamp,
denomIn: ExchangeDenomination, denomIn: ExchangeDenomination,
): DenominationRecord { ): DenominationRecord {
let denomPub: DenominationPubKey; let denomPub: DenominationPubKey;
@ -132,7 +132,9 @@ async function handleExchangeUpdateError(
} }
export function getExchangeRequestTimeout(): Duration { export function getExchangeRequestTimeout(): Duration {
return { d_ms: 5000 }; return Duration.fromSpec({
seconds: 5,
});
} }
export interface ExchangeTosDownloadResult { export interface ExchangeTosDownloadResult {
@ -362,12 +364,11 @@ export async function updateExchangeFromUrl(
async function provideExchangeRecord( async function provideExchangeRecord(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
now: Timestamp, now: AbsoluteTime,
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord | undefined; exchangeDetails: ExchangeDetailsRecord | undefined;
}> { }> {
return await ws.db return await ws.db
.mktx((x) => ({ .mktx((x) => ({
exchanges: x.exchanges, exchanges: x.exchanges,
@ -376,14 +377,14 @@ async function provideExchangeRecord(
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
let exchange = await tx.exchanges.get(baseUrl); let exchange = await tx.exchanges.get(baseUrl);
if (!exchange) { if (!exchange) {
const r = { const r: ExchangeRecord = {
permanent: true, permanent: true,
baseUrl: baseUrl, baseUrl: baseUrl,
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
detailsPointer: undefined, detailsPointer: undefined,
lastUpdate: undefined, lastUpdate: undefined,
nextUpdate: now, nextUpdate: AbsoluteTime.toTimestamp(now),
nextRefreshCheck: now, nextRefreshCheck: AbsoluteTime.toTimestamp(now),
}; };
await tx.exchanges.put(r); await tx.exchanges.put(r);
exchange = r; exchange = r;
@ -400,10 +401,10 @@ interface ExchangeKeysDownloadResult {
currentDenominations: DenominationRecord[]; currentDenominations: DenominationRecord[];
protocolVersion: string; protocolVersion: string;
signingKeys: ExchangeSignKeyJson[]; signingKeys: ExchangeSignKeyJson[];
reserveClosingDelay: Duration; reserveClosingDelay: TalerProtocolDuration;
expiry: Timestamp; expiry: TalerProtocolTimestamp;
recoup: Recoup[]; recoup: Recoup[];
listIssueDate: Timestamp; listIssueDate: TalerProtocolTimestamp;
} }
/** /**
@ -475,9 +476,11 @@ async function downloadExchangeKeysInfo(
protocolVersion: exchangeKeysJsonUnchecked.version, protocolVersion: exchangeKeysJsonUnchecked.version,
signingKeys: exchangeKeysJsonUnchecked.signkeys, signingKeys: exchangeKeysJsonUnchecked.signkeys,
reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay, reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay,
expiry: getExpiryTimestamp(resp, { expiry: AbsoluteTime.toTimestamp(
minDuration: durationFromSpec({ hours: 1 }), getExpiry(resp, {
}), minDuration: durationFromSpec({ hours: 1 }),
}),
),
recoup: exchangeKeysJsonUnchecked.recoup ?? [], recoup: exchangeKeysJsonUnchecked.recoup ?? [],
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
}; };
@ -529,12 +532,20 @@ async function updateExchangeFromUrlImpl(
exchangeDetails: ExchangeDetailsRecord; exchangeDetails: ExchangeDetailsRecord;
}> { }> {
logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`); logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`);
const now = getTimestampNow(); const now = AbsoluteTime.now();
baseUrl = canonicalizeBaseUrl(baseUrl); 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"); logger.info("using existing exchange info");
return { exchange, exchangeDetails }; return { exchange, exchangeDetails };
} }
@ -575,7 +586,8 @@ async function updateExchangeFromUrlImpl(
timeout, timeout,
acceptedFormat, acceptedFormat,
); );
const tosHasBeenAccepted = exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag const tosHasBeenAccepted =
exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
let recoupGroupId: string | undefined; let recoupGroupId: string | undefined;
@ -611,23 +623,25 @@ async function updateExchangeFromUrlImpl(
exchangeBaseUrl: r.baseUrl, exchangeBaseUrl: r.baseUrl,
wireInfo, wireInfo,
termsOfServiceText: tosDownload.tosText, termsOfServiceText: tosDownload.tosText,
termsOfServiceAcceptedEtag: tosHasBeenAccepted ? tosDownload.tosEtag : undefined, termsOfServiceAcceptedEtag: tosHasBeenAccepted
? tosDownload.tosEtag
: undefined,
termsOfServiceContentType: tosDownload.tosContentType, termsOfServiceContentType: tosDownload.tosContentType,
termsOfServiceLastEtag: tosDownload.tosEtag, termsOfServiceLastEtag: tosDownload.tosEtag,
termsOfServiceAcceptedTimestamp: getTimestampNow(), termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
}; };
// FIXME: only update if pointer got updated // FIXME: only update if pointer got updated
r.lastError = undefined; r.lastError = undefined;
r.retryInfo = initRetryInfo(); r.retryInfo = initRetryInfo();
r.lastUpdate = getTimestampNow(); r.lastUpdate = TalerProtocolTimestamp.now();
r.nextUpdate = keysInfo.expiry; r.nextUpdate = keysInfo.expiry;
// New denominations might be available. // New denominations might be available.
r.nextRefreshCheck = getTimestampNow(); r.nextRefreshCheck = TalerProtocolTimestamp.now();
r.detailsPointer = { r.detailsPointer = {
currency: details.currency, currency: details.currency,
masterPublicKey: details.masterPublicKey, masterPublicKey: details.masterPublicKey,
// FIXME: only change if pointer really changed // FIXME: only change if pointer really changed
updateClock: getTimestampNow(), updateClock: TalerProtocolTimestamp.now(),
protocolVersionRange: keysInfo.protocolVersion, protocolVersionRange: keysInfo.protocolVersion,
}; };
await tx.exchanges.put(r); await tx.exchanges.put(r);

View File

@ -27,7 +27,6 @@
import { import {
AmountJson, AmountJson,
Amounts, Amounts,
CheckPaymentResponse,
codecForContractTerms, codecForContractTerms,
codecForMerchantPayResponse, codecForMerchantPayResponse,
codecForProposal, codecForProposal,
@ -41,11 +40,8 @@ import {
durationMin, durationMin,
durationMul, durationMul,
encodeCrock, encodeCrock,
getDurationRemaining,
getRandomBytes, getRandomBytes,
getTimestampNow,
HttpStatusCode, HttpStatusCode,
isTimestampExpired,
j2s, j2s,
kdf, kdf,
Logger, Logger,
@ -57,9 +53,9 @@ import {
stringToBytes, stringToBytes,
TalerErrorCode, TalerErrorCode,
TalerErrorDetails, TalerErrorDetails,
Timestamp, AbsoluteTime,
timestampAddDuration,
URL, URL,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js";
import { import {
@ -172,7 +168,9 @@ function isSpendableCoin(coin: CoinRecord, denom: DenominationRecord): boolean {
if (coin.status !== CoinStatus.Fresh) { if (coin.status !== CoinStatus.Fresh) {
return false; return false;
} }
if (isTimestampExpired(denom.stampExpireDeposit)) { if (
AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(denom.stampExpireDeposit))
) {
return false; return false;
} }
return true; return true;
@ -187,7 +185,7 @@ export interface CoinSelectionRequest {
/** /**
* Timestamp of the contract. * Timestamp of the contract.
*/ */
timestamp: Timestamp; timestamp: TalerProtocolTimestamp;
wireMethod: string; wireMethod: string;
@ -422,7 +420,7 @@ async function recordConfirmPay(
payCoinSelectionUid: encodeCrock(getRandomBytes(32)), payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
totalPayCost: payCostInfo, totalPayCost: payCostInfo,
coinDepositPermissions, coinDepositPermissions,
timestampAccept: getTimestampNow(), timestampAccept: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
timestampLastRefundStatus: undefined, timestampLastRefundStatus: undefined,
proposalId: proposal.proposalId, proposalId: proposal.proposalId,
lastPayError: undefined, lastPayError: undefined,
@ -784,7 +782,7 @@ async function processDownloadProposalImpl(
} catch (e) { } catch (e) {
const err = makeErrorDetails( const err = makeErrorDetails(
TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED,
"schema validation failed", `schema validation failed: ${e}`,
{}, {},
); );
await failProposalPermanently(ws, proposalId, err); await failProposalPermanently(ws, proposalId, err);
@ -921,7 +919,7 @@ async function startDownloadProposal(
noncePriv: priv, noncePriv: priv,
noncePub: pub, noncePub: pub,
claimToken, claimToken,
timestamp: getTimestampNow(), timestamp: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
merchantBaseUrl, merchantBaseUrl,
orderId, orderId,
proposalId: proposalId, proposalId: proposalId,
@ -956,7 +954,7 @@ async function storeFirstPaySuccess(
sessionId: string | undefined, sessionId: string | undefined,
paySig: string, paySig: string,
): Promise<void> { ): Promise<void> {
const now = getTimestampNow(); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
await ws.db await ws.db
.mktx((x) => ({ purchases: x.purchases })) .mktx((x) => ({ purchases: x.purchases }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
@ -978,13 +976,16 @@ async function storeFirstPaySuccess(
purchase.payRetryInfo = initRetryInfo(); purchase.payRetryInfo = initRetryInfo();
purchase.merchantPaySig = paySig; purchase.merchantPaySig = paySig;
if (isFirst) { if (isFirst) {
const ar = purchase.download.contractData.autoRefund; const protoAr = purchase.download.contractData.autoRefund;
if (ar) { if (protoAr) {
const ar = Duration.fromTalerProtocolDuration(protoAr);
logger.info("auto_refund present"); logger.info("auto_refund present");
purchase.refundQueryRequested = true; purchase.refundQueryRequested = true;
purchase.refundStatusRetryInfo = initRetryInfo(); purchase.refundStatusRetryInfo = initRetryInfo();
purchase.lastRefundStatusError = undefined; purchase.lastRefundStatusError = undefined;
purchase.autoRefundDeadline = timestampAddDuration(now, ar); purchase.autoRefundDeadline = AbsoluteTime.toTimestamp(
AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
);
} }
} }
await tx.purchases.put(purchase); await tx.purchases.put(purchase);
@ -1150,7 +1151,7 @@ async function unblockBackup(
if (bp.state.tag === BackupProviderStateTag.Retrying) { if (bp.state.tag === BackupProviderStateTag.Retrying) {
bp.state = { bp.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getTimestampNow(), nextBackupTimestamp: TalerProtocolTimestamp.now(),
}; };
} }
}); });

View File

@ -35,7 +35,7 @@ import {
PendingTaskType, PendingTaskType,
ReserveType, ReserveType,
} from "../pending-types.js"; } 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 { InternalWalletState } from "../common.js";
import { GetReadOnlyAccess } from "../util/query.js"; import { GetReadOnlyAccess } from "../util/query.js";
@ -44,21 +44,25 @@ async function gatherExchangePending(
exchanges: typeof WalletStoresV1.exchanges; exchanges: typeof WalletStoresV1.exchanges;
exchangeDetails: typeof WalletStoresV1.exchangeDetails; exchangeDetails: typeof WalletStoresV1.exchangeDetails;
}>, }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.exchanges.iter().forEachAsync(async (e) => { await tx.exchanges.iter().forEachAsync(async (e) => {
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate, type: PendingTaskType.ExchangeUpdate,
givesLifeness: false, givesLifeness: false,
timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextUpdate, timestampDue: e.lastError
? e.retryInfo.nextRetry
: AbsoluteTime.fromTimestamp(e.nextUpdate),
exchangeBaseUrl: e.baseUrl, exchangeBaseUrl: e.baseUrl,
lastError: e.lastError, lastError: e.lastError,
}); });
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh, type: PendingTaskType.ExchangeCheckRefresh,
timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextRefreshCheck, timestampDue: e.lastError
? e.retryInfo.nextRetry
: AbsoluteTime.fromTimestamp(e.nextRefreshCheck),
givesLifeness: false, givesLifeness: false,
exchangeBaseUrl: e.baseUrl, exchangeBaseUrl: e.baseUrl,
}); });
@ -67,7 +71,7 @@ async function gatherExchangePending(
async function gatherReservePending( async function gatherReservePending(
tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>, tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
const reserves = await tx.reserves.indexes.byStatus.getAll( const reserves = await tx.reserves.indexes.byStatus.getAll(
@ -87,7 +91,7 @@ async function gatherReservePending(
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Reserve, type: PendingTaskType.Reserve,
givesLifeness: true, givesLifeness: true,
timestampDue: reserve.retryInfo?.nextRetry ?? Timestamp.now(), timestampDue: reserve.retryInfo?.nextRetry ?? AbsoluteTime.now(),
stage: reserve.reserveStatus, stage: reserve.reserveStatus,
timestampCreated: reserve.timestampCreated, timestampCreated: reserve.timestampCreated,
reserveType, reserveType,
@ -105,7 +109,7 @@ async function gatherReservePending(
async function gatherRefreshPending( async function gatherRefreshPending(
tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups }>, tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll( const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(
@ -136,7 +140,7 @@ async function gatherWithdrawalPending(
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
planchets: typeof WalletStoresV1.planchets; planchets: typeof WalletStoresV1.planchets;
}>, }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll( const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll(
@ -169,14 +173,14 @@ async function gatherWithdrawalPending(
async function gatherProposalPending( async function gatherProposalPending(
tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>, tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.proposals.iter().forEach((proposal) => { await tx.proposals.iter().forEach((proposal) => {
if (proposal.proposalStatus == ProposalStatus.Proposed) { if (proposal.proposalStatus == ProposalStatus.Proposed) {
// Nothing to do, user needs to choose. // Nothing to do, user needs to choose.
} else if (proposal.proposalStatus == ProposalStatus.Downloading) { } else if (proposal.proposalStatus == ProposalStatus.Downloading) {
const timestampDue = proposal.retryInfo?.nextRetry ?? getTimestampNow(); const timestampDue = proposal.retryInfo?.nextRetry ?? AbsoluteTime.now();
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.ProposalDownload, type: PendingTaskType.ProposalDownload,
givesLifeness: true, givesLifeness: true,
@ -194,7 +198,7 @@ async function gatherProposalPending(
async function gatherDepositPending( async function gatherDepositPending(
tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>, tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
const dgs = await tx.depositGroups.indexes.byStatus.getAll( const dgs = await tx.depositGroups.indexes.byStatus.getAll(
@ -204,7 +208,7 @@ async function gatherDepositPending(
if (dg.timestampFinished) { if (dg.timestampFinished) {
return; return;
} }
const timestampDue = dg.retryInfo?.nextRetry ?? getTimestampNow(); const timestampDue = dg.retryInfo?.nextRetry ?? AbsoluteTime.now();
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Deposit, type: PendingTaskType.Deposit,
givesLifeness: true, givesLifeness: true,
@ -218,7 +222,7 @@ async function gatherDepositPending(
async function gatherTipPending( async function gatherTipPending(
tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>, tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.tips.iter().forEach((tip) => { await tx.tips.iter().forEach((tip) => {
@ -240,7 +244,7 @@ async function gatherTipPending(
async function gatherPurchasePending( async function gatherPurchasePending(
tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>, tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.purchases.iter().forEach((pr) => { await tx.purchases.iter().forEach((pr) => {
@ -249,7 +253,7 @@ async function gatherPurchasePending(
pr.abortStatus === AbortStatus.None && pr.abortStatus === AbortStatus.None &&
!pr.payFrozen !pr.payFrozen
) { ) {
const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow(); const timestampDue = pr.payRetryInfo?.nextRetry ?? AbsoluteTime.now();
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Pay, type: PendingTaskType.Pay,
givesLifeness: true, givesLifeness: true,
@ -275,7 +279,7 @@ async function gatherPurchasePending(
async function gatherRecoupPending( async function gatherRecoupPending(
tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>, tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.recoupGroups.iter().forEach((rg) => { await tx.recoupGroups.iter().forEach((rg) => {
@ -297,7 +301,7 @@ async function gatherBackupPending(
tx: GetReadOnlyAccess<{ tx: GetReadOnlyAccess<{
backupProviders: typeof WalletStoresV1.backupProviders; backupProviders: typeof WalletStoresV1.backupProviders;
}>, }>,
now: Timestamp, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.backupProviders.iter().forEach((bp) => { await tx.backupProviders.iter().forEach((bp) => {
@ -305,7 +309,7 @@ async function gatherBackupPending(
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Backup, type: PendingTaskType.Backup,
givesLifeness: false, givesLifeness: false,
timestampDue: bp.state.nextBackupTimestamp, timestampDue: AbsoluteTime.fromTimestamp(bp.state.nextBackupTimestamp),
backupProviderBaseUrl: bp.baseUrl, backupProviderBaseUrl: bp.baseUrl,
lastError: undefined, lastError: undefined,
}); });
@ -325,7 +329,7 @@ async function gatherBackupPending(
export async function getPendingOperations( export async function getPendingOperations(
ws: InternalWalletState, ws: InternalWalletState,
): Promise<PendingOperationsResponse> { ): Promise<PendingOperationsResponse> {
const now = getTimestampNow(); const now = AbsoluteTime.now();
return await ws.db return await ws.db
.mktx((x) => ({ .mktx((x) => ({
backupProviders: x.backupProviders, backupProviders: x.backupProviders,

View File

@ -27,11 +27,11 @@
import { import {
Amounts, Amounts,
codecForRecoupConfirmation, codecForRecoupConfirmation,
getTimestampNow,
j2s, j2s,
NotificationType, NotificationType,
RefreshReason, RefreshReason,
TalerErrorDetails, TalerErrorDetails,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
import { import {
@ -110,7 +110,7 @@ async function putGroupAsFinished(
} }
if (allFinished) { if (allFinished) {
logger.info("all recoups of recoup group are finished"); logger.info("all recoups of recoup group are finished");
recoupGroup.timestampFinished = getTimestampNow(); recoupGroup.timestampFinished = TalerProtocolTimestamp.now();
recoupGroup.retryInfo = initRetryInfo(); recoupGroup.retryInfo = initRetryInfo();
recoupGroup.lastError = undefined; recoupGroup.lastError = undefined;
if (recoupGroup.scheduleRefreshCoins.length > 0) { if (recoupGroup.scheduleRefreshCoins.length > 0) {
@ -467,7 +467,7 @@ export async function createRecoupGroup(
coinPubs: coinPubs, coinPubs: coinPubs,
lastError: undefined, lastError: undefined,
timestampFinished: undefined, timestampFinished: undefined,
timestampStarted: getTimestampNow(), timestampStarted: TalerProtocolTimestamp.now(),
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
recoupFinishedPerCoin: coinPubs.map(() => false), recoupFinishedPerCoin: coinPubs.map(() => false),
// Will be populated later // Will be populated later

View File

@ -23,7 +23,7 @@ import {
ExchangeRefreshRevealRequest, ExchangeRefreshRevealRequest,
getRandomBytes, getRandomBytes,
HttpStatusCode, HttpStatusCode,
j2s, TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
CoinRecord, CoinRecord,
@ -42,11 +42,8 @@ import {
fnutil, fnutil,
NotificationType, NotificationType,
RefreshGroupId, RefreshGroupId,
RefreshPlanchetInfo,
RefreshReason, RefreshReason,
stringifyTimestamp,
TalerErrorDetails, TalerErrorDetails,
timestampToIsoString,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { amountToPretty } from "@gnu-taler/taler-util"; import { amountToPretty } from "@gnu-taler/taler-util";
@ -61,12 +58,7 @@ import {
Duration, Duration,
durationFromSpec, durationFromSpec,
durationMul, durationMul,
getTimestampNow, AbsoluteTime,
isTimestampExpired,
Timestamp,
timestampAddDuration,
timestampDifference,
timestampMin,
URL, URL,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { guardOperationException } from "../errors.js"; import { guardOperationException } from "../errors.js";
@ -139,7 +131,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
rg.frozen = true; rg.frozen = true;
rg.retryInfo = initRetryInfo(); rg.retryInfo = initRetryInfo();
} else { } else {
rg.timestampFinished = getTimestampNow(); rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
rg.operationStatus = OperationStatus.Finished; rg.operationStatus = OperationStatus.Finished;
rg.retryInfo = initRetryInfo(); rg.retryInfo = initRetryInfo();
} }
@ -234,19 +226,6 @@ async function refreshCreateSession(
availableDenoms, 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) { if (newCoinDenoms.selectedDenoms.length === 0) {
logger.trace( logger.trace(
`not refreshing, available amount ${amountToPretty( `not refreshing, available amount ${amountToPretty(
@ -306,7 +285,9 @@ async function refreshCreateSession(
} }
function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration { function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration {
return { d_ms: 5000 }; return Duration.fromSpec({
seconds: 5,
});
} }
async function refreshMelt( async function refreshMelt(
@ -949,12 +930,12 @@ export async function createRefreshGroup(
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
inputPerCoin, inputPerCoin,
estimatedOutputPerCoin, estimatedOutputPerCoin,
timestampCreated: getTimestampNow(), timestampCreated: TalerProtocolTimestamp.now(),
}; };
if (oldCoinPubs.length == 0) { if (oldCoinPubs.length == 0) {
logger.warn("created refresh group with zero coins"); logger.warn("created refresh group with zero coins");
refreshGroup.timestampFinished = getTimestampNow(); refreshGroup.timestampFinished = TalerProtocolTimestamp.now();
refreshGroup.operationStatus = OperationStatus.Finished; 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. * Timestamp after which the wallet would do the next check for an auto-refresh.
*/ */
function getAutoRefreshCheckThreshold(d: DenominationRecord): Timestamp { function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime {
const delta = timestampDifference( const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
d.stampExpireWithdraw, const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
d.stampExpireDeposit, const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
);
const deltaDiv = durationMul(delta, 0.75); 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. * Timestamp after which the wallet would do an auto-refresh.
*/ */
function getAutoRefreshExecuteThreshold(d: DenominationRecord): Timestamp { function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime {
const delta = timestampDifference( const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
d.stampExpireWithdraw, const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
d.stampExpireDeposit, const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
);
const deltaDiv = durationMul(delta, 0.5); const deltaDiv = durationMul(delta, 0.5);
return timestampAddDuration(d.stampExpireWithdraw, deltaDiv); return AbsoluteTime.addDuration(expireWithdraw, deltaDiv);
} }
export async function autoRefresh( export async function autoRefresh(
@ -1001,8 +980,8 @@ export async function autoRefresh(
): Promise<void> { ): Promise<void> {
logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`); logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true); await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true);
let minCheckThreshold = timestampAddDuration( let minCheckThreshold = AbsoluteTime.addDuration(
getTimestampNow(), AbsoluteTime.now(),
durationFromSpec({ days: 1 }), durationFromSpec({ days: 1 }),
); );
await ws.db await ws.db
@ -1037,11 +1016,14 @@ export async function autoRefresh(
continue; continue;
} }
const executeThreshold = getAutoRefreshExecuteThreshold(denom); const executeThreshold = getAutoRefreshExecuteThreshold(denom);
if (isTimestampExpired(executeThreshold)) { if (AbsoluteTime.isExpired(executeThreshold)) {
refreshCoins.push(coin); refreshCoins.push(coin);
} else { } else {
const checkThreshold = getAutoRefreshCheckThreshold(denom); const checkThreshold = getAutoRefreshCheckThreshold(denom);
minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold); minCheckThreshold = AbsoluteTime.min(
minCheckThreshold,
checkThreshold,
);
} }
} }
if (refreshCoins.length > 0) { if (refreshCoins.length > 0) {
@ -1056,12 +1038,12 @@ export async function autoRefresh(
); );
} }
logger.info( logger.info(
`current wallet time: ${timestampToIsoString(getTimestampNow())}`, `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`,
); );
logger.info( 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); await tx.exchanges.put(exchange);
}); });
} }

View File

@ -32,7 +32,6 @@ import {
codecForAbortResponse, codecForAbortResponse,
codecForMerchantOrderRefundPickupResponse, codecForMerchantOrderRefundPickupResponse,
CoinPublicKey, CoinPublicKey,
getTimestampNow,
Logger, Logger,
MerchantCoinRefundFailureStatus, MerchantCoinRefundFailureStatus,
MerchantCoinRefundStatus, MerchantCoinRefundStatus,
@ -43,9 +42,10 @@ import {
TalerErrorCode, TalerErrorCode,
TalerErrorDetails, TalerErrorDetails,
URL, URL,
timestampAddDuration,
codecForMerchantOrderStatusPaid, codecForMerchantOrderStatusPaid,
isTimestampExpired, AbsoluteTime,
TalerProtocolTimestamp,
Duration,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
AbortStatus, AbortStatus,
@ -170,7 +170,7 @@ async function applySuccessfulRefund(
p.refunds[refundKey] = { p.refunds[refundKey] = {
type: RefundState.Applied, type: RefundState.Applied,
obtainedTime: getTimestampNow(), obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
executionTime: r.execution_time, executionTime: r.execution_time,
refundAmount: Amounts.parseOrThrow(r.refund_amount), refundAmount: Amounts.parseOrThrow(r.refund_amount),
refundFee: denom.feeRefund, refundFee: denom.feeRefund,
@ -222,7 +222,7 @@ async function storePendingRefund(
p.refunds[refundKey] = { p.refunds[refundKey] = {
type: RefundState.Pending, type: RefundState.Pending,
obtainedTime: getTimestampNow(), obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
executionTime: r.execution_time, executionTime: r.execution_time,
refundAmount: Amounts.parseOrThrow(r.refund_amount), refundAmount: Amounts.parseOrThrow(r.refund_amount),
refundFee: denom.feeRefund, refundFee: denom.feeRefund,
@ -275,7 +275,7 @@ async function storeFailedRefund(
p.refunds[refundKey] = { p.refunds[refundKey] = {
type: RefundState.Failed, type: RefundState.Failed,
obtainedTime: getTimestampNow(), obtainedTime: TalerProtocolTimestamp.now(),
executionTime: r.execution_time, executionTime: r.execution_time,
refundAmount: Amounts.parseOrThrow(r.refund_amount), refundAmount: Amounts.parseOrThrow(r.refund_amount),
refundFee: denom.feeRefund, refundFee: denom.feeRefund,
@ -327,7 +327,7 @@ async function acceptRefunds(
reason: RefundReason, reason: RefundReason,
): Promise<void> { ): Promise<void> {
logger.trace("handling refunds", refunds); logger.trace("handling refunds", refunds);
const now = getTimestampNow(); const now = TalerProtocolTimestamp.now();
await ws.db await ws.db
.mktx((x) => ({ .mktx((x) => ({
@ -401,7 +401,10 @@ async function acceptRefunds(
if ( if (
p.timestampFirstSuccessfulPay && p.timestampFirstSuccessfulPay &&
p.autoRefundDeadline && p.autoRefundDeadline &&
p.autoRefundDeadline.t_ms > now.t_ms AbsoluteTime.cmp(
AbsoluteTime.fromTimestamp(p.autoRefundDeadline),
AbsoluteTime.fromTimestamp(now),
) > 0
) { ) {
queryDone = false; queryDone = false;
} }
@ -556,8 +559,10 @@ export async function applyRefund(
).amount, ).amount,
).amount; ).amount;
} else { } else {
amountRefundGone = Amounts.add(amountRefundGone, refund.refundAmount) amountRefundGone = Amounts.add(
.amount; amountRefundGone,
refund.refundAmount,
).amount;
} }
}); });
@ -623,7 +628,9 @@ async function processPurchaseQueryRefundImpl(
if ( if (
waitForAutoRefund && waitForAutoRefund &&
purchase.autoRefundDeadline && purchase.autoRefundDeadline &&
!isTimestampExpired(purchase.autoRefundDeadline) !AbsoluteTime.isExpired(
AbsoluteTime.fromTimestamp(purchase.autoRefundDeadline),
)
) { ) {
const requestUrl = new URL( const requestUrl = new URL(
`orders/${purchase.download.contractData.orderId}`, `orders/${purchase.download.contractData.orderId}`,
@ -731,11 +738,13 @@ async function processPurchaseQueryRefundImpl(
purchase.payCoinSelection.coinContributions[i], purchase.payCoinSelection.coinContributions[i],
), ),
rtransaction_id: 0, rtransaction_id: 0,
execution_time: timestampAddDuration( execution_time: AbsoluteTime.toTimestamp(
purchase.download.contractData.timestamp, AbsoluteTime.addDuration(
{ AbsoluteTime.fromTimestamp(
d_ms: 1000, purchase.download.contractData.timestamp,
}, ),
Duration.fromSpec({ seconds: 1 }),
),
), ),
}); });
} }

View File

@ -29,15 +29,13 @@ import {
durationMin, durationMin,
encodeCrock, encodeCrock,
getRandomBytes, getRandomBytes,
getTimestampNow,
j2s, j2s,
Logger, Logger,
NotificationType, NotificationType,
randomBytes, randomBytes,
ReserveTransactionType,
TalerErrorCode, TalerErrorCode,
TalerErrorDetails, TalerErrorDetails,
Timestamp, AbsoluteTime,
URL, URL,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../common.js"; import { InternalWalletState } from "../common.js";
@ -172,7 +170,7 @@ export async function createReserve(
req: CreateReserveRequest, req: CreateReserveRequest,
): Promise<CreateReserveResponse> { ): Promise<CreateReserveResponse> {
const keypair = await ws.cryptoApi.createEddsaKeypair(); const keypair = await ws.cryptoApi.createEddsaKeypair();
const now = getTimestampNow(); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
const canonExchange = canonicalizeBaseUrl(req.exchange); const canonExchange = canonicalizeBaseUrl(req.exchange);
let reserveStatus; let reserveStatus;
@ -217,7 +215,6 @@ export async function createReserve(
timestampReserveInfoPosted: undefined, timestampReserveInfoPosted: undefined,
bankInfo, bankInfo,
reserveStatus, reserveStatus,
lastSuccessfulStatusQuery: undefined,
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
currency: req.amount.currency, currency: req.amount.currency,
@ -403,7 +400,9 @@ async function registerReserveWithBank(
default: default:
return; return;
} }
r.timestampReserveInfoPosted = getTimestampNow(); r.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(
AbsoluteTime.now(),
);
r.reserveStatus = ReserveRecordStatus.WaitConfirmBank; r.reserveStatus = ReserveRecordStatus.WaitConfirmBank;
r.operationStatus = OperationStatus.Pending; r.operationStatus = OperationStatus.Pending;
if (!r.bankInfo) { if (!r.bankInfo) {
@ -472,7 +471,7 @@ async function processReserveBankStatus(
default: default:
return; return;
} }
const now = getTimestampNow(); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
r.timestampBankConfirmed = now; r.timestampBankConfirmed = now;
r.reserveStatus = ReserveRecordStatus.BankAborted; r.reserveStatus = ReserveRecordStatus.BankAborted;
r.operationStatus = OperationStatus.Finished; r.operationStatus = OperationStatus.Finished;
@ -509,7 +508,7 @@ async function processReserveBankStatus(
default: default:
return; return;
} }
const now = getTimestampNow(); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
r.timestampBankConfirmed = now; r.timestampBankConfirmed = now;
r.reserveStatus = ReserveRecordStatus.QueryingStatus; r.reserveStatus = ReserveRecordStatus.QueryingStatus;
r.operationStatus = OperationStatus.Pending; r.operationStatus = OperationStatus.Pending;
@ -683,7 +682,7 @@ async function updateReserve(
exchangeBaseUrl: reserve.exchangeBaseUrl, exchangeBaseUrl: reserve.exchangeBaseUrl,
reservePub: reserve.reservePub, reservePub: reserve.reservePub,
rawWithdrawalAmount: remainingAmount, rawWithdrawalAmount: remainingAmount,
timestampStart: getTimestampNow(), timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
denomsSel: denomSelectionInfoToState(denomSelInfo), denomsSel: denomSelectionInfoToState(denomSelInfo),
@ -736,7 +735,7 @@ async function processReserveImpl(
await resetReserveRetry(ws, reservePub); await resetReserveRetry(ws, reservePub);
} else if ( } else if (
reserve.retryInfo && reserve.retryInfo &&
!Timestamp.isExpired(reserve.retryInfo.nextRetry) !AbsoluteTime.isExpired(reserve.retryInfo.nextRetry)
) { ) {
logger.trace("processReserve retry not due yet"); logger.trace("processReserve retry not due yet");
return; return;

View File

@ -229,8 +229,8 @@ async function createOrder(
amount, amount,
summary, summary,
fulfillment_url: fulfillmentUrl, fulfillment_url: fulfillmentUrl,
refund_deadline: { t_ms: t * 1000 }, refund_deadline: { t_s: t },
wire_transfer_deadline: { t_ms: t * 1000 }, wire_transfer_deadline: { t_s: t },
}, },
}; };
const resp = await http.postJson(reqUrl, orderReq, { const resp = await http.postJson(reqUrl, orderReq, {

View File

@ -22,7 +22,6 @@ import {
parseTipUri, parseTipUri,
codecForTipPickupGetResponse, codecForTipPickupGetResponse,
Amounts, Amounts,
getTimestampNow,
TalerErrorDetails, TalerErrorDetails,
NotificationType, NotificationType,
TipPlanchetDetail, TipPlanchetDetail,
@ -32,6 +31,7 @@ import {
DenomKeyType, DenomKeyType,
BlindedDenominationSignature, BlindedDenominationSignature,
codecForMerchantTipResponseV2, codecForMerchantTipResponseV2,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js"; import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
import { import {
@ -39,6 +39,7 @@ import {
CoinRecord, CoinRecord,
CoinSourceType, CoinSourceType,
CoinStatus, CoinStatus,
TipRecord,
} from "../db.js"; } from "../db.js";
import { j2s } from "@gnu-taler/taler-util"; import { j2s } from "@gnu-taler/taler-util";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
@ -115,14 +116,14 @@ export async function prepareTip(
const secretSeed = encodeCrock(getRandomBytes(64)); const secretSeed = encodeCrock(getRandomBytes(64));
const denomSelUid = encodeCrock(getRandomBytes(32)); const denomSelUid = encodeCrock(getRandomBytes(32));
const newTipRecord = { const newTipRecord: TipRecord = {
walletTipId: walletTipId, walletTipId: walletTipId,
acceptedTimestamp: undefined, acceptedTimestamp: undefined,
tipAmountRaw: amount, tipAmountRaw: amount,
tipExpiration: tipPickupStatus.expiration, tipExpiration: tipPickupStatus.expiration,
exchangeBaseUrl: tipPickupStatus.exchange_url, exchangeBaseUrl: tipPickupStatus.exchange_url,
merchantBaseUrl: res.merchantBaseUrl, merchantBaseUrl: res.merchantBaseUrl,
createdTimestamp: getTimestampNow(), createdTimestamp: TalerProtocolTimestamp.now(),
merchantTipId: res.merchantTipId, merchantTipId: res.merchantTipId,
tipAmountEffective: Amounts.sub( tipAmountEffective: Amounts.sub(
amount, amount,
@ -397,7 +398,7 @@ async function processTipImpl(
if (tr.pickedUpTimestamp) { if (tr.pickedUpTimestamp) {
return; return;
} }
tr.pickedUpTimestamp = getTimestampNow(); tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
tr.lastError = undefined; tr.lastError = undefined;
tr.retryInfo = initRetryInfo(); tr.retryInfo = initRetryInfo();
await tx.tips.put(tr); await tx.tips.put(tr);
@ -421,7 +422,7 @@ export async function acceptTip(
logger.error("tip not found"); logger.error("tip not found");
return false; return false;
} }
tipRecord.acceptedTimestamp = getTimestampNow(); tipRecord.acceptedTimestamp = TalerProtocolTimestamp.now();
await tx.tips.put(tipRecord); await tx.tips.put(tipRecord);
return true; return true;
}); });

View File

@ -18,12 +18,12 @@
* Imports. * Imports.
*/ */
import { import {
AbsoluteTime,
AmountJson, AmountJson,
Amounts, Amounts,
Logger, Logger,
OrderShortInfo, OrderShortInfo,
PaymentStatus, PaymentStatus,
timestampCmp,
Transaction, Transaction,
TransactionsRequest, TransactionsRequest,
TransactionsResponse, TransactionsResponse,
@ -309,7 +309,7 @@ export async function getTransactions(
for (const rk of Object.keys(pr.refunds)) { for (const rk of Object.keys(pr.refunds)) {
const refund = pr.refunds[rk]; const refund = pr.refunds[rk];
const groupKey = `${refund.executionTime.t_ms}`; const groupKey = `${refund.executionTime.t_s}`;
refundGroupKeys.add(groupKey); refundGroupKeys.add(groupKey);
} }
@ -333,7 +333,7 @@ export async function getTransactions(
let amountEffective = Amounts.getZero(contractData.amount.currency); let amountEffective = Amounts.getZero(contractData.amount.currency);
for (const rk of Object.keys(pr.refunds)) { for (const rk of Object.keys(pr.refunds)) {
const refund = pr.refunds[rk]; const refund = pr.refunds[rk];
const myGroupKey = `${refund.executionTime.t_ms}`; const myGroupKey = `${refund.executionTime.t_s}`;
if (myGroupKey !== groupKey) { if (myGroupKey !== groupKey) {
continue; continue;
} }
@ -403,8 +403,18 @@ export async function getTransactions(
const txPending = transactions.filter((x) => x.pending); const txPending = transactions.filter((x) => x.pending);
const txNotPending = transactions.filter((x) => !x.pending); const txNotPending = transactions.filter((x) => !x.pending);
txPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp)); txPending.sort((h1, h2) =>
txNotPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp)); 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] }; return { transactions: [...txNotPending, ...txPending] };
} }
@ -485,11 +495,10 @@ export async function deleteTransaction(
}); });
return; return;
} }
const reserveRecord: const reserveRecord: ReserveRecord | undefined =
| ReserveRecord await tx.reserves.indexes.byInitialWithdrawalGroupId.get(
| undefined = await tx.reserves.indexes.byInitialWithdrawalGroupId.get( withdrawalGroupId,
withdrawalGroupId, );
);
if (reserveRecord && !reserveRecord.initialWithdrawalStarted) { if (reserveRecord && !reserveRecord.initialWithdrawalStarted) {
const reservePub = reserveRecord.reservePub; const reservePub = reserveRecord.reservePub;
await tx.reserves.delete(reservePub); await tx.reserves.delete(reservePub);

View File

@ -62,16 +62,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R", "4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -79,7 +79,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 0, fraction: 0,
value: 1000, value: 1000,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
{ {
denomPub: { denomPub: {
@ -117,16 +117,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20", "P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -134,7 +134,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 0, fraction: 0,
value: 10, value: 10,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
{ {
denomPub: { denomPub: {
@ -171,16 +171,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G", "8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -188,7 +188,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 0, fraction: 0,
value: 5, value: 5,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
{ {
denomPub: { denomPub: {
@ -226,16 +226,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610", "E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -243,7 +243,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 0, fraction: 0,
value: 1, value: 1,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
{ {
denomPub: { denomPub: {
@ -280,16 +280,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838", "0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -297,7 +297,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 10000000, fraction: 10000000,
value: 0, value: 0,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
{ {
denomPub: { denomPub: {
@ -334,16 +334,16 @@ test("withdrawal selection bug repro", (t) => {
masterSig: masterSig:
"58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R", "58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R",
stampExpireDeposit: { stampExpireDeposit: {
t_ms: 1742909388000, t_s: 1742909388,
}, },
stampExpireLegal: { stampExpireLegal: {
t_ms: 1900589388000, t_s: 1900589388,
}, },
stampExpireWithdraw: { stampExpireWithdraw: {
t_ms: 1679837388000, t_s: 1679837388,
}, },
stampStart: { stampStart: {
t_ms: 1585229388000, t_s: 1585229388,
}, },
verificationStatus: DenominationVerificationStatus.Unverified, verificationStatus: DenominationVerificationStatus.Unverified,
value: { value: {
@ -351,7 +351,7 @@ test("withdrawal selection bug repro", (t) => {
fraction: 0, fraction: 0,
value: 2, value: 2,
}, },
listIssueDate: { t_ms: 0 }, listIssueDate: { t_s: 0 },
}, },
]; ];

View File

@ -26,16 +26,12 @@ import {
codecForWithdrawResponse, codecForWithdrawResponse,
durationFromSpec, durationFromSpec,
ExchangeListItem, ExchangeListItem,
getDurationRemaining,
getTimestampNow,
Logger, Logger,
NotificationType, NotificationType,
parseWithdrawUri, parseWithdrawUri,
TalerErrorCode, TalerErrorCode,
TalerErrorDetails, TalerErrorDetails,
Timestamp, AbsoluteTime,
timestampCmp,
timestampSubtractDuraction,
WithdrawResponse, WithdrawResponse,
URL, URL,
WithdrawUriInfoResponse, WithdrawUriInfoResponse,
@ -44,6 +40,8 @@ import {
LibtoolVersion, LibtoolVersion,
UnblindedSignature, UnblindedSignature,
ExchangeWithdrawRequest, ExchangeWithdrawRequest,
Duration,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
CoinRecord, CoinRecord,
@ -147,7 +145,7 @@ export interface ExchangeWithdrawDetails {
/** /**
* The earliest deposit expiration of the selected coins. * The earliest deposit expiration of the selected coins.
*/ */
earliestDepositExpiration: Timestamp; earliestDepositExpiration: TalerProtocolTimestamp;
/** /**
* Number of currently offered denominations. * Number of currently offered denominations.
@ -184,18 +182,20 @@ export interface ExchangeWithdrawDetails {
* revocation and offered state. * revocation and offered state.
*/ */
export function isWithdrawableDenom(d: DenominationRecord): boolean { export function isWithdrawableDenom(d: DenominationRecord): boolean {
const now = getTimestampNow(); const now = AbsoluteTime.now();
const started = timestampCmp(now, d.stampStart) >= 0; const start = AbsoluteTime.fromTimestamp(d.stampStart);
let lastPossibleWithdraw: Timestamp; const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
const started = AbsoluteTime.cmp(now, start) >= 0;
let lastPossibleWithdraw: AbsoluteTime;
if (walletCoreDebugFlags.denomselAllowLate) { if (walletCoreDebugFlags.denomselAllowLate) {
lastPossibleWithdraw = d.stampExpireWithdraw; lastPossibleWithdraw = start;
} else { } else {
lastPossibleWithdraw = timestampSubtractDuraction( lastPossibleWithdraw = AbsoluteTime.subtractDuraction(
d.stampExpireWithdraw, withdrawExpire,
durationFromSpec({ minutes: 5 }), durationFromSpec({ minutes: 5 }),
); );
} }
const remaining = getDurationRemaining(lastPossibleWithdraw, now); const remaining = Duration.getRemaining(lastPossibleWithdraw, now);
const stillOkay = remaining.d_ms !== 0; const stillOkay = remaining.d_ms !== 0;
return started && stillOkay && !d.isRevoked && d.isOffered; return started && stillOkay && !d.isRevoked && d.isOffered;
} }
@ -274,7 +274,7 @@ export function selectWithdrawalDenominations(
/** /**
* Get information about a withdrawal from * Get information about a withdrawal from
* a taler://withdraw URI by asking the bank. * a taler://withdraw URI by asking the bank.
* *
* FIXME: Move into bank client. * FIXME: Move into bank client.
*/ */
export async function getBankWithdrawalInfo( export async function getBankWithdrawalInfo(
@ -947,7 +947,7 @@ async function processWithdrawGroupImpl(
logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
finishedForFirstTime = true; finishedForFirstTime = true;
wg.timestampFinish = getTimestampNow(); wg.timestampFinish = TalerProtocolTimestamp.now();
wg.operationStatus = OperationStatus.Finished; wg.operationStatus = OperationStatus.Finished;
delete wg.lastError; delete wg.lastError;
wg.retryInfo = initRetryInfo(); wg.retryInfo = initRetryInfo();
@ -999,7 +999,12 @@ export async function getExchangeWithdrawalInfo(
for (let i = 1; i < selectedDenoms.selectedDenoms.length; i++) { for (let i = 1; i < selectedDenoms.selectedDenoms.length; i++) {
const expireDeposit = const expireDeposit =
selectedDenoms.selectedDenoms[i].denom.stampExpireDeposit; selectedDenoms.selectedDenoms[i].denom.stampExpireDeposit;
if (expireDeposit.t_ms < earliestDepositExpiration.t_ms) { if (
AbsoluteTime.cmp(
AbsoluteTime.fromTimestamp(expireDeposit),
AbsoluteTime.fromTimestamp(earliestDepositExpiration),
) < 0
) {
earliestDepositExpiration = expireDeposit; earliestDepositExpiration = expireDeposit;
} }
} }

View File

@ -27,7 +27,8 @@
import { import {
TalerErrorDetails, TalerErrorDetails,
BalancesResponse, BalancesResponse,
Timestamp, AbsoluteTime,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { ReserveRecordStatus } from "./db.js"; import { ReserveRecordStatus } from "./db.js";
import { RetryInfo } from "./util/retries.js"; import { RetryInfo } from "./util/retries.js";
@ -112,7 +113,7 @@ export interface PendingReserveTask {
type: PendingTaskType.Reserve; type: PendingTaskType.Reserve;
retryInfo: RetryInfo | undefined; retryInfo: RetryInfo | undefined;
stage: ReserveRecordStatus; stage: ReserveRecordStatus;
timestampCreated: Timestamp; timestampCreated: TalerProtocolTimestamp;
reserveType: ReserveType; reserveType: ReserveType;
reservePub: string; reservePub: string;
bankWithdrawConfirmUrl?: string; bankWithdrawConfirmUrl?: string;
@ -135,7 +136,7 @@ export interface PendingRefreshTask {
export interface PendingProposalDownloadTask { export interface PendingProposalDownloadTask {
type: PendingTaskType.ProposalDownload; type: PendingTaskType.ProposalDownload;
merchantBaseUrl: string; merchantBaseUrl: string;
proposalTimestamp: Timestamp; proposalTimestamp: TalerProtocolTimestamp;
proposalId: string; proposalId: string;
orderId: string; orderId: string;
lastError?: TalerErrorDetails; lastError?: TalerErrorDetails;
@ -149,7 +150,7 @@ export interface PendingProposalDownloadTask {
export interface PendingProposalChoiceOperation { export interface PendingProposalChoiceOperation {
type: PendingTaskType.ProposalChoice; type: PendingTaskType.ProposalChoice;
merchantBaseUrl: string; merchantBaseUrl: string;
proposalTimestamp: Timestamp; proposalTimestamp: AbsoluteTime;
proposalId: string; proposalId: string;
} }
@ -231,7 +232,7 @@ export interface PendingTaskInfoCommon {
/** /**
* Timestamp when the pending operation should be executed next. * Timestamp when the pending operation should be executed next.
*/ */
timestampDue: Timestamp; timestampDue: AbsoluteTime;
/** /**
* Retry info. Currently used to stop the wallet after any operation * Retry info. Currently used to stop the wallet after any operation

View File

@ -28,10 +28,7 @@ import { OperationFailedError, makeErrorDetails } from "../errors.js";
import { import {
Logger, Logger,
Duration, Duration,
Timestamp, AbsoluteTime,
getTimestampNow,
timestampAddDuration,
timestampMax,
TalerErrorDetails, TalerErrorDetails,
Codec, Codec,
j2s, j2s,
@ -314,24 +311,24 @@ export async function readSuccessResponseTextOrThrow<T>(
/** /**
* Get the timestamp at which the response's content is considered expired. * Get the timestamp at which the response's content is considered expired.
*/ */
export function getExpiryTimestamp( export function getExpiry(
httpResponse: HttpResponse, httpResponse: HttpResponse,
opt: { minDuration?: Duration }, opt: { minDuration?: Duration },
): Timestamp { ): AbsoluteTime {
const expiryDateMs = new Date( const expiryDateMs = new Date(
httpResponse.headers.get("expiry") ?? "", httpResponse.headers.get("expiry") ?? "",
).getTime(); ).getTime();
let t: Timestamp; let t: AbsoluteTime;
if (Number.isNaN(expiryDateMs)) { if (Number.isNaN(expiryDateMs)) {
t = getTimestampNow(); t = AbsoluteTime.now();
} else { } else {
t = { t = {
t_ms: expiryDateMs, t_ms: expiryDateMs,
}; };
} }
if (opt.minDuration) { if (opt.minDuration) {
const t2 = timestampAddDuration(getTimestampNow(), opt.minDuration); const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration);
return timestampMax(t, t2); return AbsoluteTime.max(t, t2);
} }
return t; return t;
} }

View File

@ -21,11 +21,11 @@
/** /**
* Imports. * Imports.
*/ */
import { Timestamp, Duration, getTimestampNow } from "@gnu-taler/taler-util"; import { AbsoluteTime, Duration } from "@gnu-taler/taler-util";
export interface RetryInfo { export interface RetryInfo {
firstTry: Timestamp; firstTry: AbsoluteTime;
nextRetry: Timestamp; nextRetry: AbsoluteTime;
retryCounter: number; retryCounter: number;
} }
@ -45,7 +45,7 @@ export function updateRetryInfoTimeout(
r: RetryInfo, r: RetryInfo,
p: RetryPolicy = defaultRetryPolicy, p: RetryPolicy = defaultRetryPolicy,
): void { ): void {
const now = getTimestampNow(); const now = AbsoluteTime.now();
if (now.t_ms === "never") { if (now.t_ms === "never") {
throw Error("assertion failed"); throw Error("assertion failed");
} }
@ -54,10 +54,14 @@ export function updateRetryInfoTimeout(
return; 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 = 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 }; r.nextRetry = { t_ms: t };
} }
@ -73,13 +77,13 @@ export function getRetryDuration(
return { d_ms: "forever" }; return { d_ms: "forever" };
} }
const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); 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( export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
p: RetryPolicy = defaultRetryPolicy, const now = AbsoluteTime.now();
): RetryInfo {
const now = getTimestampNow();
const info = { const info = {
firstTry: now, firstTry: now,
nextRetry: now, nextRetry: now,

View File

@ -64,9 +64,7 @@ import {
durationMin, durationMin,
ExchangeListItem, ExchangeListItem,
ExchangesListRespose, ExchangesListRespose,
getDurationRemaining,
GetExchangeTosResult, GetExchangeTosResult,
isTimestampExpired,
j2s, j2s,
KnownBankAccounts, KnownBankAccounts,
Logger, Logger,
@ -76,11 +74,12 @@ import {
PaytoUri, PaytoUri,
RefreshReason, RefreshReason,
TalerErrorCode, TalerErrorCode,
Timestamp, AbsoluteTime,
timestampMin,
URL, URL,
WalletNotification, WalletNotification,
Duration,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { timeStamp } from "console";
import { import {
DenomInfo, DenomInfo,
ExchangeOperations, ExchangeOperations,
@ -292,7 +291,7 @@ export async function runPending(
): Promise<void> { ): Promise<void> {
const pendingOpsResponse = await getPendingOperations(ws); const pendingOpsResponse = await getPendingOperations(ws);
for (const p of pendingOpsResponse.pendingOperations) { for (const p of pendingOpsResponse.pendingOperations) {
if (!forceNow && !isTimestampExpired(p.timestampDue)) { if (!forceNow && !AbsoluteTime.isExpired(p.timestampDue)) {
continue; continue;
} }
try { try {
@ -340,10 +339,10 @@ async function runTaskLoop(
logger.trace(`pending operations: ${j2s(pending)}`); logger.trace(`pending operations: ${j2s(pending)}`);
let numGivingLiveness = 0; let numGivingLiveness = 0;
let numDue = 0; let numDue = 0;
let minDue: Timestamp = { t_ms: "never" }; let minDue: AbsoluteTime = AbsoluteTime.never();
for (const p of pending.pendingOperations) { for (const p of pending.pendingOperations) {
minDue = timestampMin(minDue, p.timestampDue); minDue = AbsoluteTime.min(minDue, p.timestampDue);
if (isTimestampExpired(p.timestampDue)) { if (AbsoluteTime.isExpired(p.timestampDue)) {
numDue++; numDue++;
} }
if (p.givesLifeness) { if (p.givesLifeness) {
@ -377,7 +376,7 @@ async function runTaskLoop(
durationFromSpec({ durationFromSpec({
seconds: 5, seconds: 5,
}), }),
getDurationRemaining(minDue), Duration.getRemaining(minDue),
); );
logger.trace(`waiting for at most ${dt.d_ms} ms`); logger.trace(`waiting for at most ${dt.d_ms} ms`);
const timeout = ws.timerGroup.resolveAfter(dt); const timeout = ws.timerGroup.resolveAfter(dt);
@ -394,7 +393,7 @@ async function runTaskLoop(
`running ${pending.pendingOperations.length} pending operations`, `running ${pending.pendingOperations.length} pending operations`,
); );
for (const p of pending.pendingOperations) { for (const p of pending.pendingOperations) {
if (!isTimestampExpired(p.timestampDue)) { if (!AbsoluteTime.isExpired(p.timestampDue)) {
continue; continue;
} }
try { try {

View File

@ -22,7 +22,11 @@
import { PendingTransactionsView as TestedComponent } from "./PendingTransactions"; import { PendingTransactionsView as TestedComponent } from "./PendingTransactions";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { Transaction, TransactionType } from "@gnu-taler/taler-util"; import {
TalerProtocolTimestamp,
Transaction,
TransactionType,
} from "@gnu-taler/taler-util";
export default { export default {
title: "component/PendingTransactions", title: "component/PendingTransactions",
@ -34,9 +38,7 @@ export const OnePendingTransaction = createExample(TestedComponent, {
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
], ],
}); });
@ -46,23 +48,17 @@ export const ThreePendingTransactions = createExample(TestedComponent, {
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
], ],
}); });
@ -72,72 +68,52 @@ export const TenPendingTransactions = createExample(TestedComponent, {
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1)
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
{ {
amountEffective: "USD:10", amountEffective: "USD:10",
type: TransactionType.Withdrawal, type: TransactionType.Withdrawal,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 1,
},
} as Transaction, } as Transaction,
], ],
}); });

View File

@ -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 { PendingTaskInfo } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, JSX } from "preact"; import { Fragment, h, JSX } from "preact";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
@ -70,7 +75,11 @@ export function PendingTransactionsView({
<b> <b>
{amount.currency} {Amounts.stringifyValue(amount)} {amount.currency} {Amounts.stringifyValue(amount)}
</b>{" "} </b>{" "}
- <Time timestamp={t.timestamp} format="dd MMMM yyyy" /> -{" "}
<Time
timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
format="dd MMMM yyyy"
/>
</Typography> </Typography>
), ),
}; };

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { formatISO, format } from "date-fns";
import { h, VNode } from "preact"; import { h, VNode } from "preact";
@ -22,7 +22,7 @@ export function Time({
timestamp, timestamp,
format: formatString, format: formatString,
}: { }: {
timestamp: Timestamp | undefined; timestamp: AbsoluteTime | undefined;
format: string; format: string;
}): VNode { }): VNode {
return ( return (

View File

@ -18,7 +18,7 @@ import {
AmountJson, AmountJson,
Amounts, Amounts,
AmountString, AmountString,
Timestamp, AbsoluteTime,
Transaction, Transaction,
TransactionType, TransactionType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
@ -46,7 +46,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.exchangeBaseUrl).hostname} title={new URL(tx.exchangeBaseUrl).hostname}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"W"} iconPath={"W"}
pending={tx.pending} pending={tx.pending}
/> />
@ -59,7 +59,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
title={tx.info.merchant.name} title={tx.info.merchant.name}
subtitle={tx.info.summary} subtitle={tx.info.summary}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"P"} iconPath={"P"}
pending={tx.pending} pending={tx.pending}
/> />
@ -72,7 +72,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
subtitle={tx.info.summary} subtitle={tx.info.summary}
title={tx.info.merchant.name} title={tx.info.merchant.name}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"R"} iconPath={"R"}
pending={tx.pending} pending={tx.pending}
/> />
@ -84,7 +84,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.merchantBaseUrl).hostname} title={new URL(tx.merchantBaseUrl).hostname}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"T"} iconPath={"T"}
pending={tx.pending} pending={tx.pending}
/> />
@ -96,7 +96,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.exchangeBaseUrl).hostname} title={new URL(tx.exchangeBaseUrl).hostname}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"R"} iconPath={"R"}
pending={tx.pending} pending={tx.pending}
/> />
@ -108,7 +108,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
title={tx.targetPaytoUri} title={tx.targetPaytoUri}
timestamp={tx.timestamp} timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"D"} iconPath={"D"}
pending={tx.pending} pending={tx.pending}
/> />
@ -165,7 +165,7 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
interface TransactionLayoutProps { interface TransactionLayoutProps {
debitCreditIndicator: "debit" | "credit" | "unknown"; debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown"; amount: AmountString | "unknown";
timestamp: Timestamp; timestamp: AbsoluteTime;
title: string; title: string;
subtitle?: string; subtitle?: string;
id: string; id: string;

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { AbsoluteTime, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { View as TestedComponent } from "./Tip"; import { View as TestedComponent } from "./Tip";
@ -33,9 +34,7 @@ export const Accepted = createExample(TestedComponent, {
accepted: true, accepted: true,
merchantBaseUrl: "", merchantBaseUrl: "",
exchangeBaseUrl: "", exchangeBaseUrl: "",
expirationTimestamp: { expirationTimestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 0,
},
tipAmountEffective: "USD:10", tipAmountEffective: "USD:10",
tipAmountRaw: "USD:5", tipAmountRaw: "USD:5",
walletTipId: "id", walletTipId: "id",
@ -47,9 +46,7 @@ export const NotYetAccepted = createExample(TestedComponent, {
accepted: false, accepted: false,
merchantBaseUrl: "http://merchant.url/", merchantBaseUrl: "http://merchant.url/",
exchangeBaseUrl: "http://exchange.url/", exchangeBaseUrl: "http://exchange.url/",
expirationTimestamp: { expirationTimestamp: TalerProtocolTimestamp.fromSeconds(1),
t_ms: 0,
},
tipAmountEffective: "USD:10", tipAmountEffective: "USD:10",
tipAmountRaw: "USD:5", tipAmountRaw: "USD:5",
walletTipId: "id", walletTipId: "id",

View File

@ -20,7 +20,7 @@ import {
CoinDumpJson, CoinDumpJson,
ExchangeListItem, ExchangeListItem,
NotificationType, NotificationType,
Timestamp, AbsoluteTime,
Translate, Translate,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core"; import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core";

View File

@ -23,6 +23,7 @@ import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
import { addDays } from "date-fns"; import { addDays } from "date-fns";
import { BackupView as TestedComponent } from "./BackupPage"; import { BackupView as TestedComponent } from "./BackupPage";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
export default { export default {
title: "wallet/backup/list", title: "wallet/backup/list",
@ -40,9 +41,8 @@ export const LotOfProviders = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],
@ -62,9 +62,8 @@ export const LotOfProviders = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],
@ -172,9 +171,8 @@ export const OneProvider = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 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 { import {
ProviderInfo, ProviderInfo,
ProviderPaymentPaid, ProviderPaymentPaid,
@ -104,7 +104,13 @@ export function BackupView({
<BackupLayout <BackupLayout
key={idx} key={idx}
status={provider.paymentStatus} status={provider.paymentStatus}
timestamp={provider.lastSuccessfulBackupTimestamp} timestamp={
provider.lastSuccessfulBackupTimestamp
? AbsoluteTime.fromTimestamp(
provider.lastSuccessfulBackupTimestamp,
)
: undefined
}
id={provider.syncProviderBaseUrl} id={provider.syncProviderBaseUrl}
active={provider.active} active={provider.active}
title={provider.name} title={provider.name}
@ -144,7 +150,7 @@ export function BackupView({
interface TransactionLayoutProps { interface TransactionLayoutProps {
status: ProviderPaymentStatus; status: ProviderPaymentStatus;
timestamp?: Timestamp; timestamp?: AbsoluteTime;
title: string; title: string;
id: string; id: string;
active: boolean; 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(); const { i18n } = useTranslationContext();
return ( return (
<Fragment> <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)"; if (d.t_ms === "never") return "rgb(28, 184, 65)";
const months = differenceInMonths(d.t_ms, new Date()); const months = differenceInMonths(d.t_ms, new Date());
return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)"; 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 ""; if (d.t_ms === "never") return "";
const duration = intervalToDuration({ const duration = intervalToDuration({
start: d.t_ms, start: d.t_ms,

View File

@ -21,6 +21,7 @@
import { import {
PaymentStatus, PaymentStatus,
TalerProtocolTimestamp,
TransactionCommon, TransactionCommon,
TransactionDeposit, TransactionDeposit,
TransactionPayment, TransactionPayment,
@ -45,9 +46,9 @@ const commonTransaction = () =>
amountRaw: "USD:10", amountRaw: "USD:10",
amountEffective: "USD:9", amountEffective: "USD:9",
pending: false, pending: false,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(
t_ms: new Date().getTime() - count++ * 1000 * 60 * 60 * 7, new Date().getTime() - count++ * 60 * 60 * 7,
}, ),
transactionId: "12", transactionId: "12",
} as TransactionCommon); } as TransactionCommon);

View File

@ -126,7 +126,7 @@ export function HistoryView({
.filter((t) => t.amountRaw.split(":")[0] === selectedCurrency) .filter((t) => t.amountRaw.split(":")[0] === selectedCurrency)
.reduce((rv, x) => { .reduce((rv, x) => {
const theDate = 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) { if (theDate) {
(rv[theDate] = rv[theDate] || []).push(x); (rv[theDate] = rv[theDate] || []).push(x);
} }

View File

@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
import { createExample } from "../test-utils"; import { createExample } from "../test-utils";
import { ProviderView as TestedComponent } from "./ProviderDetailPage"; import { ProviderView as TestedComponent } from "./ProviderDetailPage";
@ -38,9 +39,8 @@ export const Active = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],
@ -63,12 +63,10 @@ export const ActiveErrorSync = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
}, lastAttemptedBackupTimestamp:
lastAttemptedBackupTimestamp: { TalerProtocolTimestamp.fromSeconds(1625063925078),
t_ms: 1625063925078,
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],
@ -97,9 +95,8 @@ export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],
@ -125,9 +122,8 @@ export const ActiveBackupProblemDevice = createExample(TestedComponent, {
active: true, active: true,
name: "sync.demo", name: "sync.demo",
syncProviderBaseUrl: "http://sync.taler:9967/", syncProviderBaseUrl: "http://sync.taler:9967/",
lastSuccessfulBackupTimestamp: { lastSuccessfulBackupTimestamp:
t_ms: 1625063925078, TalerProtocolTimestamp.fromSeconds(1625063925078),
},
paymentProposalIds: [ paymentProposalIds: [
"43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG", "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
], ],

View File

@ -15,6 +15,7 @@
*/ */
import * as utils from "@gnu-taler/taler-util"; import * as utils from "@gnu-taler/taler-util";
import { AbsoluteTime } from "@gnu-taler/taler-util";
import { import {
ProviderInfo, ProviderInfo,
ProviderPaymentStatus, ProviderPaymentStatus,
@ -122,7 +123,9 @@ export function ProviderView({
</Fragment> </Fragment>
); );
} }
const lb = info.lastSuccessfulBackupTimestamp; const lb = info.lastSuccessfulBackupTimestamp
? AbsoluteTime.fromTimestamp(info.lastSuccessfulBackupTimestamp)
: undefined;
const isPaid = const isPaid =
info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.Paid ||
info.paymentStatus.type === ProviderPaymentType.TermsChanged; info.paymentStatus.type === ProviderPaymentType.TermsChanged;

View File

@ -21,6 +21,7 @@
import { import {
PaymentStatus, PaymentStatus,
TalerProtocolTimestamp,
TransactionCommon, TransactionCommon,
TransactionDeposit, TransactionDeposit,
TransactionPayment, TransactionPayment,
@ -53,9 +54,7 @@ const commonTransaction = {
amountRaw: "KUDOS:11", amountRaw: "KUDOS:11",
amountEffective: "KUDOS:9.2", amountEffective: "KUDOS:9.2",
pending: false, pending: false,
timestamp: { timestamp: TalerProtocolTimestamp.now(),
t_ms: new Date().getTime(),
},
transactionId: "12", transactionId: "12",
} as TransactionCommon; } as TransactionCommon;
@ -144,18 +143,14 @@ export const Withdraw = createExample(TestedComponent, {
export const WithdrawOneMinuteAgo = createExample(TestedComponent, { export const WithdrawOneMinuteAgo = createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.withdraw, ...exampleData.withdraw,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
t_ms: new Date().getTime() - 60 * 1000,
},
}, },
}); });
export const WithdrawOneMinuteAgoAndPending = createExample(TestedComponent, { export const WithdrawOneMinuteAgoAndPending = createExample(TestedComponent, {
transaction: { transaction: {
...exampleData.withdraw, ...exampleData.withdraw,
timestamp: { timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
t_ms: new Date().getTime() - 60 * 1000,
},
pending: true, pending: true,
}, },
}); });

View File

@ -15,6 +15,7 @@
*/ */
import { import {
AbsoluteTime,
AmountLike, AmountLike,
Amounts, Amounts,
NotificationType, NotificationType,
@ -137,9 +138,9 @@ export function TransactionView({
}): VNode { }): VNode {
const showRetry = const showRetry =
transaction.error !== undefined || transaction.error !== undefined ||
transaction.timestamp.t_ms === "never" || transaction.timestamp.t_s === "never" ||
(transaction.pending && (transaction.pending &&
differenceInSeconds(new Date(), transaction.timestamp.t_ms) > 10); differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) > 10);
return ( return (
<Fragment> <Fragment>
@ -218,7 +219,7 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Withdrawal</i18n.Translate> <i18n.Translate>Withdrawal</i18n.Translate>
</h2> </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.pending ? (
transaction.withdrawalDetails.type === transaction.withdrawalDetails.type ===
WithdrawalType.ManualTransfer ? ( WithdrawalType.ManualTransfer ? (
@ -342,7 +343,7 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Payment</i18n.Translate> <i18n.Translate>Payment</i18n.Translate>
</h2> </h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} format="dd MMMM yyyy, HH:mm" />
<br /> <br />
<Part <Part
big big
@ -425,7 +426,7 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Deposit</i18n.Translate> <i18n.Translate>Deposit</i18n.Translate>
</h2> </h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} format="dd MMMM yyyy, HH:mm" />
<br /> <br />
<Part <Part
big big
@ -459,7 +460,7 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Refresh</i18n.Translate> <i18n.Translate>Refresh</i18n.Translate>
</h2> </h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} format="dd MMMM yyyy, HH:mm" />
<br /> <br />
<Part <Part
big big
@ -493,7 +494,7 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Tip</i18n.Translate> <i18n.Translate>Tip</i18n.Translate>
</h2> </h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} format="dd MMMM yyyy, HH:mm" />
<br /> <br />
<Part <Part
big big
@ -527,7 +528,10 @@ export function TransactionView({
<h2> <h2>
<i18n.Translate>Refund</i18n.Translate> <i18n.Translate>Refund</i18n.Translate>
</h2> </h2>
<Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <Time
timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
format="dd MMMM yyyy, HH:mm"
/>
<br /> <br />
<Part <Part
big big