Skip to content

Commit 0b29033

Browse files
committed
🩹 app: ground date calculations in block timestamp
1 parent f7c278c commit 0b29033

14 files changed

Lines changed: 138 additions & 123 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@exactly/mobile": patch
3+
---
4+
5+
🩹 ground date calculations in block timestamp

‎src/components/loans/LoanSummary.tsx‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useBytecode } from "wagmi";
77

88
import { previewerAddress } from "@exactly/common/generated/chain";
99
import { useReadPreviewerPreviewBorrowAtMaturity } from "@exactly/common/generated/hooks";
10-
import { MATURITY_INTERVAL, WAD } from "@exactly/lib";
10+
import { WAD } from "@exactly/lib";
1111

1212
import useAccount from "../../utils/useAccount";
1313
import useAsset from "../../utils/useAsset";
@@ -25,20 +25,21 @@ export default function LoanSummary({ loan }: { loan: Loan }) {
2525
} = useTranslation();
2626
const { address } = useAccount();
2727
const { data: bytecode } = useBytecode({ address: previewerAddress, query: { enabled: !!address } });
28-
const { market, isFetching: isMarketFetching } = useAsset(loan.market);
28+
const { market, timestamp, isFetching: isMarketFetching } = useAsset(loan.market);
2929
const symbol = market?.symbol.slice(3) === "WETH" ? "ETH" : market?.symbol.slice(3);
3030
const isBorrow = loan.installments === 1;
31-
const timestamp = useMemo(() => Math.floor(Date.now() / 1000), []);
32-
const defaultMaturity = timestamp - (timestamp % MATURITY_INTERVAL) + MATURITY_INTERVAL;
33-
const { data: installments, isFetching: isInstallmentsPending } = useInstallments({
34-
timestamp: loan.maturity ? Number(loan.maturity) : defaultMaturity,
31+
const {
32+
data: installments,
33+
firstMaturity,
34+
isFetching: isInstallmentsPending,
35+
} = useInstallments({
3536
totalAmount: loan.amount ?? 0n,
3637
installments: loan.installments ?? 1,
3738
marketAddress: market?.market,
3839
});
3940
const { data: borrow, isLoading: isBorrowPending } = useReadPreviewerPreviewBorrowAtMaturity({
4041
address: previewerAddress,
41-
args: loan.market && loan.amount ? [loan.market, loan.maturity ?? BigInt(defaultMaturity), loan.amount] : undefined,
42+
args: loan.market && loan.amount ? [loan.market, loan.maturity ?? BigInt(firstMaturity), loan.amount] : undefined,
4243
query: {
4344
enabled: isBorrow && !!loan.amount && !!loan.market && !!address && !!bytecode,
4445
},
@@ -48,10 +49,9 @@ export default function LoanSummary({ loan }: { loan: Loan }) {
4849
const value =
4950
!isBorrow && installments
5051
? Number(installments.effectiveRate) / 1e18
51-
: borrow && loan.amount && loan.amount > 0n && borrow.maturity > BigInt(timestamp)
52+
: borrow && loan.amount && loan.amount > 0n && borrow.maturity > timestamp
5253
? Number(
53-
((borrow.assets - loan.amount) * WAD * 31_536_000n) /
54-
(loan.amount * (borrow.maturity - BigInt(timestamp))),
54+
((borrow.assets - loan.amount) * WAD * 31_536_000n) / (loan.amount * (borrow.maturity - timestamp)),
5555
) / 1e18
5656
: null;
5757
return (

‎src/components/loans/Maturity.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { presentArticle } from "../../utils/intercom";
1717
import queryClient from "../../utils/queryClient";
1818
import reportError from "../../utils/reportError";
1919
import useAccount from "../../utils/useAccount";
20+
import useMarkets from "../../utils/useMarkets";
2021
import SafeView from "../shared/SafeView";
2122
import Button from "../shared/StyledButton";
2223
import Text from "../shared/Text";
@@ -32,8 +33,9 @@ export default function Maturity() {
3233
} = useTranslation();
3334
const { address } = useAccount();
3435
const { data: loan } = useQuery<Loan>({ queryKey: ["loan"], enabled: !!address });
35-
const timestamp = Math.floor(Date.now() / 1000);
36-
const firstMaturity = timestamp - (timestamp % MATURITY_INTERVAL) + MATURITY_INTERVAL;
36+
const { timestamp } = useMarkets();
37+
const now = Number(timestamp);
38+
const firstMaturity = now - (now % MATURITY_INTERVAL) + MATURITY_INTERVAL;
3739

3840
const disabled = !loan?.maturity;
3941

‎src/components/loans/Review.tsx‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { useMemo, useState } from "react";
22
import { useTranslation } from "react-i18next";
33
import { Pressable } from "react-native";
44

@@ -66,7 +66,7 @@ export default function Review() {
6666
market: zeroAddress,
6767
receiver: "",
6868
};
69-
const { market: assetMarket, isFetching: isAssetPending } = useAsset(market);
69+
const { market: assetMarket, timestamp, isFetching: isAssetPending } = useAsset(market);
7070

7171
const symbol = assetMarket?.symbol.slice(3) === "WETH" ? "ETH" : assetMarket?.symbol.slice(3);
7272
const singleInstallment = count === 1;
@@ -84,7 +84,6 @@ export default function Review() {
8484
totalAmount: amount ?? 0n,
8585
installments: count ?? 0,
8686
marketAddress: market,
87-
timestamp: Number(maturity),
8887
});
8988

9089
const installmentsAmount = singleInstallment
@@ -157,11 +156,18 @@ export default function Review() {
157156
onError: reportError,
158157
});
159158

160-
const timestamp = Math.floor(Date.now() / 1000);
159+
const maturityLabel = useMemo(
160+
() =>
161+
new Date(Number(maturity) * 1000).toLocaleDateString(language, {
162+
year: "numeric",
163+
month: "short",
164+
day: "numeric",
165+
}),
166+
[maturity, language],
167+
);
161168
const rate = borrow
162169
? Number(
163-
((borrow.assets - (amount ?? 0n)) * WAD * 31_536_000n) /
164-
((amount ?? 0n) * (borrow.maturity - BigInt(timestamp))),
170+
((borrow.assets - (amount ?? 0n)) * WAD * 31_536_000n) / ((amount ?? 0n) * (borrow.maturity - timestamp)),
165171
) / 1e18
166172
: split
167173
? Number(split.effectiveRate) / 1e18
@@ -318,11 +324,7 @@ export default function Review() {
318324
{singleInstallment ? t("Installment due") : t("First installment due")}
319325
</Text>
320326
<Text headline color="$uiNeutralPrimary">
321-
{new Date(Number(maturity) * 1000).toLocaleDateString(language, {
322-
year: "numeric",
323-
month: "short",
324-
day: "numeric",
325-
})}
327+
{maturityLabel}
326328
</Text>
327329
</XStack>
328330
{!singleInstallment && (

‎src/components/pay/Calculator.tsx‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ export default function Calculator() {
4141
}
4242
return minIndex !== undefined && (rates.installments[minIndex]?.rate ?? 0n) > 0n ? minIndex : undefined;
4343
}, [rates]);
44+
const firstMaturityLabel = useMemo(
45+
() =>
46+
rates
47+
? new Date(rates.firstMaturity * 1000).toLocaleDateString(language, {
48+
year: "numeric",
49+
month: "short",
50+
day: "numeric",
51+
})
52+
: undefined,
53+
[rates, language],
54+
);
4455

4556
return (
4657
<SafeView fullScreen backgroundColor="$backgroundSoft" paddingBottom={0}>
@@ -207,11 +218,7 @@ export default function Calculator() {
207218
{rates && (
208219
<Text caption color="$uiNeutralSecondary">
209220
{t("First due date: {{date}} - then every 28 days.", {
210-
date: new Date(rates.firstMaturity * 1000).toLocaleDateString(language, {
211-
year: "numeric",
212-
month: "short",
213-
day: "numeric",
214-
}),
221+
date: firstMaturityLabel,
215222
})}
216223
</Text>
217224
)}

‎src/components/pay/PaymentSheet.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default function PaymentSheet({ onRolloverIntro }: { onRolloverIntro?: (m
4646
query: { refetchOnMount: true, enabled: !!address && !!credential },
4747
});
4848
const isLatestPlugin = installedPlugins?.[0] === exaPluginAddress;
49-
const { market: USDCMarket } = useAsset(marketUSDCAddress);
49+
const { market: USDCMarket, timestamp } = useAsset(marketUSDCAddress);
5050
const [infoOpen, setInfoOpen] = useState(false);
5151
const [open, setOpen] = useState(() => !!maturity);
5252
const [displayMaturity, setDisplayMaturity] = useState(maturity);
@@ -83,7 +83,7 @@ export default function PaymentSheet({ onRolloverIntro }: { onRolloverIntro?: (m
8383
const positionValue = ((position.position.principal + position.position.fee) * usdPrice) / 10n ** BigInt(decimals);
8484
const discount = Number(WAD - (previewValue * WAD) / positionValue) / 1e18;
8585
const dueDate = new Date(Number(displayMaturity) * 1000);
86-
const now = new Date();
86+
const now = new Date(Number(timestamp) * 1000);
8787
const isUpcoming = isAfter(dueDate, now);
8888
const timeDistance = formatDistanceStrict(isUpcoming ? now : dueDate, isUpcoming ? dueDate : now, {
8989
locale: dateFnsLocale,
@@ -92,7 +92,7 @@ export default function PaymentSheet({ onRolloverIntro }: { onRolloverIntro?: (m
9292
? t("Due in {{time}}", { time: timeDistance })
9393
: t("{{time}} past due", { time: timeDistance });
9494
return { discount, dueDate, dueStatus, isUpcoming, positionValue, previewValue };
95-
}, [displayMaturity, USDCMarket, dateFnsLocale, t]);
95+
}, [displayMaturity, USDCMarket, timestamp, dateFnsLocale, t]);
9696

9797
const close = useCallback(() => {
9898
setInfoOpen(false);

‎src/components/pay/Repay.tsx‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export default function Repay() {
6767
const { address: account } = useAccount();
6868
const router = useRouter();
6969
const { assets } = usePortfolio({ sortBy: "usdcFirst" });
70-
const { market: exaUSDC } = useAsset(marketUSDCAddress);
70+
const { market: exaUSDC, timestamp } = useAsset(marketUSDCAddress);
7171
const [enableSimulations, setEnableSimulations] = useState(true);
7272
const [assetSelectionOpen, setAssetSelectionOpen] = useState(false);
7373
const [denyExchanges, addDeniedExchange] = useReducer(
@@ -141,8 +141,7 @@ export default function Repay() {
141141
const { data: proposalDelay, isLoading: isProposalDelayLoading } = useReadProposalManagerDelay({
142142
address: proposalManagerAddress,
143143
});
144-
const simulationTimestamp =
145-
proposalDelay === undefined ? undefined : Math.floor(Date.now() / 1000) + Number(proposalDelay);
144+
const simulationTimestamp = proposalDelay === undefined ? undefined : Number(timestamp) + Number(proposalDelay);
146145

147146
const borrow = exaUSDC?.fixedBorrowPositions.find((b) => b.maturity === maturity);
148147
const previewValueUSD =
@@ -515,13 +514,17 @@ export default function Repay() {
515514

516515
const symbol =
517516
repayMarket?.symbol.slice(3) === "WETH" ? "ETH" : (repayMarket?.symbol.slice(3) ?? externalAsset?.symbol);
518-
const dueDateFormatted = maturity
519-
? new Date(Number(maturity) * 1000).toLocaleDateString(language, {
520-
year: "numeric",
521-
month: "short",
522-
day: "numeric",
523-
})
524-
: "";
517+
const dueDateFormatted = useMemo(
518+
() =>
519+
maturity
520+
? new Date(Number(maturity) * 1000).toLocaleDateString(language, {
521+
year: "numeric",
522+
month: "short",
523+
day: "numeric",
524+
})
525+
: "",
526+
[maturity, language],
527+
);
525528

526529
const handleButtonText = () => {
527530
if (repayAssets === 0n) return t("Enter amount");
@@ -820,6 +823,7 @@ export default function Repay() {
820823
return (
821824
<Pending
822825
maturity={maturity}
826+
timestamp={timestamp}
823827
amount={displayValues.amount}
824828
repayAssets={repayAssets}
825829
currency={symbol}
@@ -837,6 +841,7 @@ export default function Repay() {
837841
return (
838842
<Success
839843
maturity={maturity}
844+
timestamp={timestamp}
840845
amount={displayValues.amount}
841846
repayAssets={repayAssets}
842847
currency={symbol}
@@ -850,6 +855,7 @@ export default function Repay() {
850855
return (
851856
<Failure
852857
maturity={maturity}
858+
timestamp={timestamp}
853859
amount={displayValues.amount}
854860
repayAssets={repayAssets}
855861
currency={symbol}

‎src/components/roll-debt/RollDebt.tsx‎

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback } from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import { useTranslation } from "react-i18next";
33
import { Pressable } from "react-native";
44
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -39,15 +39,29 @@ export default function Pay() {
3939
const { address } = useAccount();
4040
const insets = useSafeAreaInsets();
4141
const router = useRouter();
42-
const { market: exaUSDC } = useAsset(marketUSDCAddress);
42+
const { market: exaUSDC, timestamp } = useAsset(marketUSDCAddress);
4343
const { success, output: repayMaturity } = safeParse(
4444
pipe(string(), nonEmpty("no maturity")),
4545
useLocalSearchParams().maturity,
4646
);
4747

48-
const timestamp = Math.floor(Date.now() / 1000);
49-
const nextMaturity = timestamp - (timestamp % MATURITY_INTERVAL) + MATURITY_INTERVAL;
50-
const borrowMaturity = Number(repayMaturity) < timestamp ? nextMaturity : Number(repayMaturity) + MATURITY_INTERVAL;
48+
const now = Number(timestamp);
49+
const nextMaturity = now - (now % MATURITY_INTERVAL) + MATURITY_INTERVAL;
50+
const borrowMaturity = Number(repayMaturity) < now ? nextMaturity : Number(repayMaturity) + MATURITY_INTERVAL;
51+
const repayLabel = useMemo(
52+
() =>
53+
new Date(Number(repayMaturity) * 1000).toLocaleDateString(language, {
54+
year: "numeric",
55+
month: "short",
56+
day: "numeric",
57+
}),
58+
[repayMaturity, language],
59+
);
60+
const borrowLabel = useMemo(
61+
() =>
62+
new Date(borrowMaturity * 1000).toLocaleDateString(language, { year: "numeric", month: "short", day: "numeric" }),
63+
[borrowMaturity, language],
64+
);
5165
const borrow = exaUSDC?.fixedBorrowPositions.find((b) => b.maturity === BigInt(success ? repayMaturity : 0));
5266
const rolloverMaturityBorrow = exaUSDC?.fixedBorrowPositions.find((b) => b.maturity === BigInt(borrowMaturity));
5367

@@ -99,13 +113,7 @@ export default function Pay() {
99113
{t("Debt to rollover")}
100114
</Text>
101115
<Text secondary footnote textAlign="left">
102-
{t("due {{date}}", {
103-
date: new Date(Number(repayMaturity) * 1000).toLocaleDateString(language, {
104-
year: "numeric",
105-
month: "short",
106-
day: "numeric",
107-
}),
108-
})}
116+
{t("due {{date}}", { date: repayLabel })}
109117
</Text>
110118
</YStack>
111119
<Text primary title3 textAlign="right">
@@ -127,7 +135,7 @@ export default function Pay() {
127135
rate: (
128136
Number(
129137
((borrowPreview.assets - borrow.previewValue) * WAD * 31_536_000n) /
130-
(borrow.previewValue * (borrowPreview.maturity - BigInt(timestamp))),
138+
(borrow.previewValue * (borrowPreview.maturity - timestamp)),
131139
) /
132140
1e18 /
133141
100
@@ -156,13 +164,7 @@ export default function Pay() {
156164
{t("Current debt")}
157165
</Text>
158166
<Text secondary footnote textAlign="left">
159-
{t("due {{date}}", {
160-
date: new Date(borrowMaturity * 1000).toLocaleDateString(language, {
161-
year: "numeric",
162-
month: "short",
163-
day: "numeric",
164-
}),
165-
})}
167+
{t("due {{date}}", { date: borrowLabel })}
166168
</Text>
167169
</YStack>
168170
<Text primary title3 textAlign="right">
@@ -180,11 +182,7 @@ export default function Pay() {
180182
{t("Total after rollover")}
181183
</Text>
182184
<Text secondary footnote textAlign="left">
183-
{new Date(borrowMaturity * 1000).toLocaleDateString(language, {
184-
year: "numeric",
185-
month: "short",
186-
day: "numeric",
187-
})}
185+
{borrowLabel}
188186
</Text>
189187
</YStack>
190188
<Text title color="$uiBrandSecondary" textAlign="right">

0 commit comments

Comments
 (0)