Skip to content

Commit ce222ba

Browse files
committed
♻️ app: flatten simulate-proposal api and batch contract reads
1 parent b5c910f commit ce222ba

7 files changed

Lines changed: 371 additions & 294 deletions

File tree

.changeset/bold-ram-dig.md

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+
♻️ flatten simulate-proposal api and batch contract reads

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"mload",
104104
"modelcontextprotocol",
105105
"moti",
106+
"multicall",
106107
"mysten",
107108
"natspec",
108109
"nfmelendez",

src/components/pay/Repay.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -282,31 +282,34 @@ export default function Repay() {
282282
const maxAmountIn = route?.fromAmount ? pad(route.fromAmount, SLIPPAGE_DIVISOR) + 69n : undefined; // HACK try to avoid ZERO_SHARES on dust deposit
283283

284284
const {
285-
propose: { data: repayPropose },
286-
executeProposal: { error: repayExecuteProposalError, isPending: isSimulatingRepay },
285+
request: repayPropose,
286+
error: repayExecuteProposalError,
287+
isPending: isSimulatingRepay,
287288
} = useSimulateProposal({
288289
account,
289290
amount: maxRepay,
290291
market: selectedAsset.address,
291-
enabled: enableSimulations && mode === "repay" && positionAssets > 0n,
292292
proposalType: ProposalType.RepayAtMaturity,
293293
maturity,
294294
positionAssets,
295+
enabled: enableSimulations && mode === "repay" && positionAssets > 0n,
295296
});
296297

297298
const {
298-
propose: { data: crossRepayPropose },
299-
executeProposal: { error: crossRepayExecuteProposalError, isPending: isSimulatingCrossRepay },
299+
request: crossRepayPropose,
300+
error: crossRepayExecuteProposalError,
301+
isPending: isSimulatingCrossRepay,
300302
} = useSimulateProposal({
301303
account,
302304
amount: maxAmountIn,
303305
market: selectedAsset.address,
304-
enabled: enableSimulations && mode === "crossRepay" && positionAssets > 0n,
305306
proposalType: ProposalType.CrossRepayAtMaturity,
307+
marketOut: marketUSDCAddress,
306308
maturity,
307309
positionAssets,
308310
maxRepay,
309311
route: route?.data,
312+
enabled: enableSimulations && mode === "crossRepay" && positionAssets > 0n && !!route,
310313
});
311314

312315
const {
@@ -378,15 +381,15 @@ export default function Repay() {
378381
switch (mode) {
379382
case "repay":
380383
if (!repayPropose) throw new Error("no repay simulation");
381-
mutate(repayPropose.request);
384+
mutate(repayPropose);
382385
break;
383386
case "legacyRepay":
384387
if (!legacyRepaySimulation) throw new Error("no legacy repay simulation");
385388
mutate(legacyRepaySimulation.request);
386389
break;
387390
case "crossRepay":
388391
if (!crossRepayPropose) throw new Error("no cross repay simulation");
389-
mutate(crossRepayPropose.request);
392+
mutate(crossRepayPropose);
390393
break;
391394
case "legacyCrossRepay":
392395
if (!legacyCrossRepaySimulation) throw new Error("no legacy cross repay simulation");

src/components/roll-debt/RollDebt.tsx

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ import { useToastController } from "@tamagui/toast";
1010
import { ScrollView, Separator, Spinner, XStack, YStack } from "tamagui";
1111

1212
import { nonEmpty, pipe, safeParse, string } from "valibot";
13-
import { ContractFunctionExecutionError, encodeAbiParameters } from "viem";
14-
import { useBytecode, useWriteContract } from "wagmi";
13+
import { ContractFunctionExecutionError } from "viem";
14+
import { useWriteContract } from "wagmi";
1515

1616
import { exaPreviewerAddress, marketUSDCAddress, previewerAddress } from "@exactly/common/generated/chain";
1717
import {
1818
useReadExaPreviewerPendingProposals,
1919
useReadPreviewerPreviewBorrowAtMaturity,
20-
useSimulateExaPluginPropose,
2120
} from "@exactly/common/generated/hooks";
2221
import ProposalType from "@exactly/common/ProposalType";
2322
import { MATURITY_INTERVAL, WAD } from "@exactly/lib";
@@ -28,6 +27,7 @@ import View from "../../components/shared/View";
2827
import reportError from "../../utils/reportError";
2928
import useAccount from "../../utils/useAccount";
3029
import useAsset from "../../utils/useAsset";
30+
import useSimulateProposal from "../../utils/useSimulateProposal";
3131
import Button from "../shared/Button";
3232
import Skeleton from "../shared/Skeleton";
3333

@@ -51,12 +51,10 @@ export default function Pay() {
5151
const borrow = exaUSDC?.fixedBorrowPositions.find((b) => b.maturity === BigInt(success ? repayMaturity : 0));
5252
const rolloverMaturityBorrow = exaUSDC?.fixedBorrowPositions.find((b) => b.maturity === BigInt(borrowMaturity));
5353

54-
const { data: bytecode } = useBytecode({ address, query: { enabled: !!address } });
55-
5654
const { data: borrowPreview } = useReadPreviewerPreviewBorrowAtMaturity({
5755
address: previewerAddress,
5856
args: [marketUSDCAddress, BigInt(borrowMaturity), borrow?.previewValue ?? 0n],
59-
query: { enabled: !!bytecode && !!exaUSDC && !!borrow && !!address && !!borrowMaturity },
57+
query: { enabled: !!exaUSDC && !!borrow && !!address && !!borrowMaturity },
6058
});
6159

6260
if (!success || !exaUSDC || !borrow) return null;
@@ -228,35 +226,22 @@ function RolloverButton({
228226
const { t } = useTranslation();
229227
const { address } = useAccount();
230228
const router = useRouter();
231-
const { data: bytecode } = useBytecode({ address, query: { enabled: !!address } });
232229
const toast = useToastController();
233230

234231
const slippage = (WAD * 105n) / 100n;
235232
const maxRepayAssets = (borrow.previewValue * slippage) / WAD;
236233
const percentage = WAD;
237234

238-
const { data: proposeSimulation } = useSimulateExaPluginPropose({
239-
address,
240-
args: [
241-
marketUSDCAddress,
242-
maxRepayAssets,
243-
ProposalType.RollDebt,
244-
encodeAbiParameters(
245-
[
246-
{
247-
type: "tuple",
248-
components: [
249-
{ name: "repayMaturity", type: "uint256" },
250-
{ name: "borrowMaturity", type: "uint256" },
251-
{ name: "maxRepayAssets", type: "uint256" },
252-
{ name: "percentage", type: "uint256" },
253-
],
254-
},
255-
],
256-
[{ repayMaturity, borrowMaturity, maxRepayAssets, percentage }],
257-
),
258-
],
259-
query: { enabled: !!address && !!bytecode },
235+
const { request: proposeSimulation, error: executeProposalError } = useSimulateProposal({
236+
account: address,
237+
amount: maxRepayAssets,
238+
market: marketUSDCAddress,
239+
proposalType: ProposalType.RollDebt,
240+
borrowMaturity,
241+
maxRepayAssets,
242+
percentage,
243+
repayMaturity,
244+
enabled: !!address,
260245
});
261246

262247
const {
@@ -266,7 +251,7 @@ function RolloverButton({
266251
} = useReadExaPreviewerPendingProposals({
267252
address: exaPreviewerAddress,
268253
args: address ? [address] : undefined,
269-
query: { enabled: !!address && !!bytecode, gcTime: 0, refetchInterval: 30_000 },
254+
query: { enabled: !!address, gcTime: 0, refetchInterval: 30_000 },
270255
});
271256

272257
const {
@@ -281,7 +266,7 @@ function RolloverButton({
281266
duration: 1000,
282267
burntOptions: { haptic: "success", preset: "done" },
283268
});
284-
if (address && bytecode) refetchPendingProposals().catch(reportError);
269+
if (address) refetchPendingProposals().catch(reportError);
285270
router.dismissTo("/activity");
286271
},
287272
onError: (error) => {
@@ -298,7 +283,7 @@ function RolloverButton({
298283
const proposeRollDebt = useCallback(() => {
299284
if (!address) throw new Error("no address");
300285
if (!proposeSimulation) throw new Error("no propose roll debt simulation");
301-
mutate(proposeSimulation.request);
286+
mutate(proposeSimulation);
302287
}, [address, proposeSimulation, mutate]);
303288

304289
const hasProposed = pendingProposals?.some(
@@ -316,7 +301,12 @@ function RolloverButton({
316301
);
317302

318303
const disabled =
319-
!!isError || isProposeRollDebtPending || isPendingProposalsPending || !proposeSimulation || hasProposed;
304+
!!isError ||
305+
!!executeProposalError ||
306+
isProposeRollDebtPending ||
307+
isPendingProposalsPending ||
308+
!proposeSimulation ||
309+
hasProposed;
320310
return (
321311
<Button
322312
onPress={proposeRollDebt}

src/components/send-funds/Amount.tsx

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,12 @@ import { Avatar, ScrollView, Square, XStack, YStack } from "tamagui";
1010
import { useForm, useStore } from "@tanstack/react-form";
1111
import { useQuery } from "@tanstack/react-query";
1212
import { bigint, check, parse, pipe, safeParse } from "valibot";
13-
import { encodeAbiParameters, erc20Abi, formatUnits, parseUnits, zeroAddress as viemZeroAddress } from "viem";
14-
import { useBytecode, useEstimateGas, useSendTransaction, useSimulateContract, useWriteContract } from "wagmi";
13+
import { erc20Abi, formatUnits, parseUnits, zeroAddress as viemZeroAddress } from "viem";
14+
import { useEstimateGas, useSendTransaction, useSimulateContract, useWriteContract } from "wagmi";
1515

1616
import accountInit from "@exactly/common/accountInit";
1717
import { exaPluginAddress } from "@exactly/common/generated/chain";
18-
import {
19-
exaPluginAbi,
20-
upgradeableModularAccountAbi,
21-
useReadUpgradeableModularAccountGetInstalledPlugins,
22-
} from "@exactly/common/generated/hooks";
18+
import { useReadUpgradeableModularAccountGetInstalledPlugins } from "@exactly/common/generated/hooks";
2319
import ProposalType from "@exactly/common/ProposalType";
2420
import shortenHex from "@exactly/common/shortenHex";
2521
import { Address, type Credential } from "@exactly/common/validation";
@@ -29,6 +25,7 @@ import ReviewSheet from "./ReviewSheet";
2925
import queryClient from "../../utils/queryClient";
3026
import useAccount from "../../utils/useAccount";
3127
import useAsset from "../../utils/useAsset";
28+
import useSimulateProposal from "../../utils/useSimulateProposal";
3229
import AmountSelector from "../shared/AmountSelector";
3330
import AssetLogo from "../shared/AssetLogo";
3431
import Blocky from "../shared/Blocky";
@@ -63,7 +60,6 @@ export default function Amount() {
6360
const formAmount = useStore(form.store, (state) => state.values.amount);
6461

6562
const { data: credential } = useQuery<Credential>({ queryKey: ["credential"] });
66-
const { data: bytecode } = useBytecode({ address, query: { enabled: !!address } });
6763
const { data: installedPlugins } = useReadUpgradeableModularAccountGetInstalledPlugins({
6864
address,
6965
factory: credential?.factory,
@@ -72,45 +68,14 @@ export default function Amount() {
7268
});
7369
const isLatestPlugin = installedPlugins?.[0] === exaPluginAddress;
7470

75-
const { data: proposeSimulation } = useSimulateContract(
76-
isLatestPlugin
77-
? {
78-
address,
79-
functionName: "propose",
80-
abi: [...upgradeableModularAccountAbi, ...exaPluginAbi],
81-
args: [
82-
market?.market ?? zeroAddress,
83-
formAmount,
84-
ProposalType.Withdraw,
85-
encodeAbiParameters([{ type: "address" }], [receiver ?? zeroAddress]),
86-
],
87-
query: {
88-
enabled: !!market && !!address && !!bytecode && formAmount > 0n && !!receiver && receiver !== zeroAddress,
89-
},
90-
}
91-
: {
92-
address,
93-
functionName: "propose",
94-
abi: [
95-
...upgradeableModularAccountAbi,
96-
{
97-
type: "function",
98-
name: "propose",
99-
inputs: [
100-
{ internalType: "contract IMarket", name: "market", type: "address" },
101-
{ internalType: "uint256", name: "amount", type: "uint256" },
102-
{ internalType: "address", name: "receiver", type: "address" },
103-
],
104-
outputs: [],
105-
stateMutability: "nonpayable",
106-
},
107-
],
108-
args: [withdrawAsset ?? zeroAddress, formAmount, receiver ?? zeroAddress],
109-
query: {
110-
enabled: !!market && !!address && !!bytecode && formAmount > 0n && !!receiver && receiver !== zeroAddress,
111-
},
112-
},
113-
);
71+
const { request: proposeSimulation } = useSimulateProposal({
72+
account: address,
73+
amount: formAmount,
74+
market: market?.market,
75+
proposalType: ProposalType.Withdraw,
76+
receiver,
77+
enabled: !!market && !!address && formAmount > 0n && !!receiver && receiver !== zeroAddress,
78+
});
11479

11580
const externalAddress = useMemo(() => {
11681
const { success, output } = safeParse(Address, external?.address);
@@ -126,13 +91,7 @@ export default function Amount() {
12691
args: receiver ? [receiver, formAmount] : undefined,
12792
query: {
12893
enabled:
129-
!!external &&
130-
!isNativeTransfer &&
131-
!!address &&
132-
!!bytecode &&
133-
formAmount > 0n &&
134-
!!receiver &&
135-
receiver !== zeroAddress,
94+
!!external && !isNativeTransfer && !!address && formAmount > 0n && !!receiver && receiver !== zeroAddress,
13695
},
13796
});
13897

@@ -188,7 +147,7 @@ export default function Amount() {
188147
const handleSubmit = useCallback(() => {
189148
if (!sendReady || !receiver) return;
190149
if (proposeSimulation) {
191-
sendContract(proposeSimulation.request);
150+
sendContract(proposeSimulation);
192151
} else if (isNativeTransfer) {
193152
sendNative({ to: receiver, value: formAmount });
194153
} else if (erc20TransferSimulation) {

src/components/swaps/Swaps.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default function Swaps() {
111111
(token: undefined | { external: boolean; token: Token }) => {
112112
if (!token) return;
113113
if (token.external) return parse(Address, token.token.address);
114-
return protocolAssets.find((a) => a.asset === token.token.address)?.market ?? zeroAddress;
114+
return protocolAssets.find((a) => a.asset === token.token.address)?.market;
115115
},
116116
[protocolAssets],
117117
);
@@ -253,8 +253,9 @@ export default function Swaps() {
253253
}, [activeInput, route]);
254254

255255
const {
256-
propose: { data: swapPropose },
257-
executeProposal: { error: swapExecuteProposalError, isPending: isSimulatingSwap },
256+
request: swapPropose,
257+
error: swapExecuteProposalError,
258+
isPending: isSimulatingSwap,
258259
} = useSimulateProposal({
259260
account,
260261
amount: activeInput === "from" ? fromAmount : (fromAmount * (WAD * (1000n + SLIPPAGE_PERCENT))) / 1000n / WAD,
@@ -345,7 +346,7 @@ export default function Swaps() {
345346
if (fromToken?.external && externalSwap) {
346347
mutate(externalSwap.request);
347348
} else if (swapPropose) {
348-
mutate(swapPropose.request);
349+
mutate(swapPropose);
349350
}
350351
updateSwap((old) => ({ ...old, enableSimulations: false }));
351352
}, [route, fromToken?.external, externalSwap, swapPropose, mutate]);

0 commit comments

Comments
 (0)