@cofhe/react 0.0.0-beta-20260324063313 → 0.0.0-beta-20260324094644

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -581,12 +581,12 @@ var require_assert = __commonJS({
581
581
  "use strict";
582
582
  Object.defineProperty(exports, "__esModule", { value: true });
583
583
  exports.assert = void 0;
584
- function assert24(condition, message = "no additional info provided") {
584
+ function assert26(condition, message = "no additional info provided") {
585
585
  if (!condition) {
586
586
  throw new Error("Assertion Error: " + message);
587
587
  }
588
588
  }
589
- exports.assert = assert24;
589
+ exports.assert = assert26;
590
590
  }
591
591
  });
592
592
 
@@ -730,7 +730,7 @@ var require_dist = __commonJS({
730
730
  });
731
731
 
732
732
  // src/providers/CofheProvider.tsx
733
- import { createContext as createContext4, useContext as useContext5, useMemo as useMemo33, useState as useState15 } from "react";
733
+ import { createContext as createContext4, useContext as useContext5, useMemo as useMemo34, useState as useState15 } from "react";
734
734
 
735
735
  // src/providers/QueryProvider.tsx
736
736
  import { QueryClient } from "@tanstack/react-query";
@@ -1066,6 +1066,71 @@ async function maybeWaitUntilRpcAwareAndReadContract(publicClient, params, optio
1066
1066
  throw new Error("Unexpected exit from wait loop");
1067
1067
  }
1068
1068
 
1069
+ // src/utils/cofheErrors.ts
1070
+ import errorsJson from "@fhenixprotocol/cofhe-errors/errors.json";
1071
+ import { ContractFunctionExecutionError, ContractFunctionRevertedError } from "viem";
1072
+ function is0xString(value) {
1073
+ return value.startsWith("0x");
1074
+ }
1075
+ function isStringArray(value) {
1076
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
1077
+ }
1078
+ function isCofheSolidityError(value) {
1079
+ if (typeof value !== "object" || value === null) return false;
1080
+ if (!("name" in value) || typeof value.name !== "string") return false;
1081
+ if (!("selector" in value) || typeof value.selector !== "string") return false;
1082
+ if (!is0xString(value.selector)) return false;
1083
+ if (!("signature" in value) || typeof value.signature !== "string") return false;
1084
+ if (!("source" in value) || typeof value.source !== "string") return false;
1085
+ if (!("inputs" in value) || !isStringArray(value.inputs)) return false;
1086
+ if (!("inputTypes" in value) || !isStringArray(value.inputTypes)) return false;
1087
+ return true;
1088
+ }
1089
+ function parseCofheSolidityErrors(value) {
1090
+ if (!Array.isArray(value)) return [];
1091
+ return value.filter(isCofheSolidityError);
1092
+ }
1093
+ var errorsJsonUnknown = errorsJson;
1094
+ var errors = parseCofheSolidityErrors(errorsJsonUnknown);
1095
+ function normalizeSelector(selector) {
1096
+ if (typeof selector !== "string") return void 0;
1097
+ const s = selector.trim().toLowerCase();
1098
+ if (!s.startsWith("0x")) return void 0;
1099
+ if (s.length < 10) return void 0;
1100
+ const candidate = s.slice(0, 10);
1101
+ if (!is0xString(candidate)) return void 0;
1102
+ return candidate;
1103
+ }
1104
+ function decodeCofheErrorSelector(selectorOrRevertData) {
1105
+ const selector = normalizeSelector(selectorOrRevertData);
1106
+ if (!selector) return void 0;
1107
+ return errors.find((e) => e.selector.toLowerCase() === selector);
1108
+ }
1109
+ function cofheHumanizeRevertReason(reasonOfRevert) {
1110
+ const decoded = decodeCofheErrorSelector(reasonOfRevert);
1111
+ if (!decoded) return void 0;
1112
+ return `${decoded.signature} (${decoded.source})`;
1113
+ }
1114
+ function extractSelectorFromMessage(message) {
1115
+ const match = message.match(/0x[0-9a-fA-F]{8}/);
1116
+ return match?.[0];
1117
+ }
1118
+ function cofheHumanizeViemError(error) {
1119
+ if (error instanceof ContractFunctionExecutionError) {
1120
+ if (error.cause instanceof ContractFunctionRevertedError) {
1121
+ const raw = error.cause.raw;
1122
+ if (typeof raw === "string") {
1123
+ return cofheHumanizeRevertReason(raw);
1124
+ }
1125
+ }
1126
+ }
1127
+ if (error instanceof Error) {
1128
+ const selector = extractSelectorFromMessage(error.message);
1129
+ if (selector) return cofheHumanizeRevertReason(selector);
1130
+ }
1131
+ return void 0;
1132
+ }
1133
+
1069
1134
  // src/components/FnxFloatingButton/FloatingButtonComponent.tsx
1070
1135
  import CloseIcon from "@mui/icons-material/Close";
1071
1136
 
@@ -1105,13 +1170,15 @@ var TransactionActionType = /* @__PURE__ */ ((TransactionActionType2) => {
1105
1170
  TransactionActionType2["Shield"] = "shield";
1106
1171
  TransactionActionType2["Unshield"] = "unshield";
1107
1172
  TransactionActionType2["Claim"] = "claim";
1173
+ TransactionActionType2["Approve"] = "approve";
1108
1174
  return TransactionActionType2;
1109
1175
  })(TransactionActionType || {});
1110
1176
  var actionToStringMap = {
1111
1177
  ["shieldSend" /* ShieldSend */]: "Shielded Transfer",
1112
1178
  ["shield" /* Shield */]: "Shield",
1113
1179
  ["unshield" /* Unshield */]: "Unshield",
1114
- ["claim" /* Claim */]: "Claim"
1180
+ ["claim" /* Claim */]: "Claim",
1181
+ ["approve" /* Approve */]: "Approve"
1115
1182
  };
1116
1183
  var actionToString = (a) => actionToStringMap[a];
1117
1184
  var statusToStringMap = {
@@ -1260,7 +1327,7 @@ var useCofhePublicClient = () => useCofheConnection().publicClient;
1260
1327
  var useCofheWalletClient = () => useCofheConnection().walletClient;
1261
1328
 
1262
1329
  // src/hooks/useTrackPendingTransactions.ts
1263
- var import_ts_essentials5 = __toESM(require_dist());
1330
+ var import_ts_essentials6 = __toESM(require_dist());
1264
1331
 
1265
1332
  // src/hooks/useCofheReadContract.ts
1266
1333
  import "@tanstack/react-query";
@@ -1388,19 +1455,27 @@ function useIsCofheErrorActive() {
1388
1455
 
1389
1456
  // src/hooks/useCofheReadContract.ts
1390
1457
  import { transformEncryptedReturnTypes } from "@cofhe/abi";
1391
- var QUERY_CACHE_PREFIX = "cofheReadContract";
1458
+
1459
+ // src/utils/serializeBigint.ts
1460
+ function isRecord(value) {
1461
+ return typeof value === "object" && value !== null;
1462
+ }
1392
1463
  function serializeIfBigint(value) {
1393
- if (typeof value === "bigint") {
1394
- return value.toString();
1395
- }
1464
+ if (typeof value === "bigint") return value.toString();
1465
+ return value;
1396
1466
  }
1397
1467
  function serializeBigintRecursively(value) {
1398
- if (Array.isArray(value)) {
1399
- return value.map(serializeBigintRecursively);
1400
- } else {
1401
- return serializeIfBigint(value);
1468
+ if (Array.isArray(value)) return value.map(serializeBigintRecursively);
1469
+ if (isRecord(value)) {
1470
+ const out = {};
1471
+ for (const [key, nested] of Object.entries(value)) out[key] = serializeBigintRecursively(nested);
1472
+ return out;
1402
1473
  }
1474
+ return serializeIfBigint(value);
1403
1475
  }
1476
+
1477
+ // src/hooks/useCofheReadContract.ts
1478
+ var QUERY_CACHE_PREFIX = "cofheReadContract";
1404
1479
  function constructCofheReadContractQueryKey({
1405
1480
  cofheChainId,
1406
1481
  address,
@@ -2235,8 +2310,86 @@ function useCofheTokenClaimable({ accountAddress: account, token }, queryOptions
2235
2310
  };
2236
2311
  }
2237
2312
 
2313
+ // src/hooks/useTokenAllowance.ts
2314
+ var import_ts_essentials4 = __toESM(require_dist(), 1);
2315
+ import "@tanstack/react-query";
2316
+ import "viem";
2317
+ function constructTokenAllowanceQueryKey({
2318
+ chainId,
2319
+ tokenAddress,
2320
+ ownerAddress,
2321
+ spenderAddress
2322
+ }) {
2323
+ return [
2324
+ "tokenAllowance",
2325
+ chainId,
2326
+ tokenAddress?.toLowerCase(),
2327
+ ownerAddress?.toLowerCase(),
2328
+ spenderAddress?.toLowerCase()
2329
+ ];
2330
+ }
2331
+ function constructTokenAllowanceQueryKeyForInvalidation({
2332
+ chainId,
2333
+ tokenAddress,
2334
+ ownerAddress,
2335
+ spenderAddress
2336
+ }) {
2337
+ return constructTokenAllowanceQueryKey({
2338
+ chainId,
2339
+ tokenAddress,
2340
+ ownerAddress,
2341
+ spenderAddress
2342
+ });
2343
+ }
2344
+ function createTokenAllowanceQueryOptions(params) {
2345
+ const { publicClient, tokenAddress, ownerAddress, spenderAddress, queryOptions } = params;
2346
+ const { enabled: userEnabled, ...restQueryOptions } = queryOptions ?? {};
2347
+ const baseEnabled = !!publicClient && !!tokenAddress && !!ownerAddress && !!spenderAddress;
2348
+ const enabled = baseEnabled && (userEnabled ?? true);
2349
+ const queryKey = constructTokenAllowanceQueryKey({
2350
+ chainId: publicClient?.chain?.id,
2351
+ tokenAddress,
2352
+ ownerAddress,
2353
+ spenderAddress
2354
+ });
2355
+ return {
2356
+ queryKey,
2357
+ queryFn: async () => {
2358
+ (0, import_ts_essentials4.assert)(publicClient, "PublicClient is required to fetch token allowance");
2359
+ (0, import_ts_essentials4.assert)(tokenAddress, "Token address is required to fetch token allowance");
2360
+ (0, import_ts_essentials4.assert)(ownerAddress, "Owner address is required to fetch token allowance");
2361
+ (0, import_ts_essentials4.assert)(spenderAddress, "Spender address is required to fetch token allowance");
2362
+ return publicClient.readContract({
2363
+ address: tokenAddress,
2364
+ abi: ERC20_ALLOWANCE_ABI,
2365
+ functionName: "allowance",
2366
+ args: [ownerAddress, spenderAddress]
2367
+ });
2368
+ },
2369
+ enabled,
2370
+ refetchOnMount: false,
2371
+ ...restQueryOptions
2372
+ };
2373
+ }
2374
+ function useTokenAllowance({ tokenAddress, ownerAddress, spenderAddress }, options) {
2375
+ const publicClient = useCofhePublicClient();
2376
+ const queryOptions = createTokenAllowanceQueryOptions({
2377
+ publicClient,
2378
+ tokenAddress,
2379
+ ownerAddress,
2380
+ spenderAddress,
2381
+ queryOptions: options
2382
+ });
2383
+ const { data, isFetching, refetch } = useInternalQuery(queryOptions);
2384
+ return {
2385
+ data,
2386
+ isFetching,
2387
+ refetch
2388
+ };
2389
+ }
2390
+
2238
2391
  // src/hooks/useStoredTransactions.ts
2239
- var import_ts_essentials4 = __toESM(require_dist());
2392
+ var import_ts_essentials5 = __toESM(require_dist());
2240
2393
  import { useMemo as useMemo6 } from "react";
2241
2394
  function useStoredTransactions({
2242
2395
  chainId,
@@ -2269,7 +2422,7 @@ function useStoredTransactions({
2269
2422
  return reducer?.(filtered);
2270
2423
  }, [filtered, reducer]);
2271
2424
  const uniqueHashes = new Set(filtered.map((tx) => tx.hash));
2272
- (0, import_ts_essentials4.assert)(
2425
+ (0, import_ts_essentials5.assert)(
2273
2426
  uniqueHashes.size === filtered.length,
2274
2427
  "There should be no duplicate transactions with the same hash in the result"
2275
2428
  );
@@ -2687,6 +2840,7 @@ var usePortalToasts = create7()((set, get) => ({
2687
2840
  }));
2688
2841
 
2689
2842
  // src/hooks/useTransactionGlobalLifecycle.ts
2843
+ import { ContractFunctionExecutionError as ContractFunctionExecutionError2, ContractFunctionRevertedError as ContractFunctionRevertedError2 } from "viem";
2690
2844
  var TOAST_DELAY_MS = 1e4;
2691
2845
  function useTransactionGlobalToastsLifecycle() {
2692
2846
  const { addToast } = usePortalToasts();
@@ -2708,12 +2862,21 @@ function useTransactionGlobalToastsLifecycle() {
2708
2862
  );
2709
2863
  },
2710
2864
  onTransactionSubmitError: (error, transactionType) => {
2865
+ let humanReadableError;
2866
+ if (error instanceof ContractFunctionExecutionError2) {
2867
+ if (error.cause instanceof ContractFunctionRevertedError2) {
2868
+ const reasonOfRevert = error.cause.raw;
2869
+ if (typeof reasonOfRevert === "string") {
2870
+ humanReadableError = cofheHumanizeRevertReason(reasonOfRevert);
2871
+ }
2872
+ }
2873
+ }
2711
2874
  console.error("____ Transaction submission failed for type:", transactionType, "with error:", error);
2712
2875
  addToast(
2713
2876
  {
2714
2877
  variant: "error",
2715
2878
  title: `${transactionType} Transaction Submission Failed`,
2716
- description: error instanceof Error ? error.message : "An unknown error occurred during transaction submission."
2879
+ description: humanReadableError ?? (error instanceof Error ? error.message : "An unknown error occurred during transaction submission.")
2717
2880
  },
2718
2881
  TOAST_DELAY_MS
2719
2882
  );
@@ -2835,7 +2998,7 @@ function invalidatePublicTokenBalanceQueries({
2835
2998
  function invalidatePublicAndConfidentialTokenBalanceQueries(token, accountAddress, queryClient) {
2836
2999
  invalidateConfidentialTokenBalanceQueries(token, queryClient);
2837
3000
  const publicPairTokenAddress = token.extensions.fhenix.erc20Pair?.address;
2838
- (0, import_ts_essentials5.assert)(publicPairTokenAddress, "Public pair token address is required for shield transaction invalidation");
3001
+ (0, import_ts_essentials6.assert)(publicPairTokenAddress, "Public pair token address is required for shield transaction invalidation");
2839
3002
  invalidatePublicTokenBalanceQueries(
2840
3003
  {
2841
3004
  tokenAddress: publicPairTokenAddress,
@@ -2845,6 +3008,20 @@ function invalidatePublicAndConfidentialTokenBalanceQueries(token, accountAddres
2845
3008
  queryClient
2846
3009
  );
2847
3010
  }
3011
+ function invalidateTokenAllowanceQueries({
3012
+ chainId,
3013
+ tokenAddress,
3014
+ ownerAddress,
3015
+ spenderAddress
3016
+ }, queryClient) {
3017
+ const queryKey = constructTokenAllowanceQueryKeyForInvalidation({
3018
+ chainId,
3019
+ tokenAddress,
3020
+ ownerAddress,
3021
+ spenderAddress
3022
+ });
3023
+ queryClient.invalidateQueries({ queryKey });
3024
+ }
2848
3025
  function useTrackPendingTransactionsBase({
2849
3026
  onReceiptSuccess,
2850
3027
  onReceiptFail,
@@ -2855,9 +3032,11 @@ function useTrackPendingTransactionsBase({
2855
3032
  const queries = accountsPendingTxs.map((tx) => ({
2856
3033
  queryKey: ["tx-receipt", tx.chainId, tx.hash],
2857
3034
  queryFn: async () => {
2858
- (0, import_ts_essentials5.assert)(publicClient, "Public client is guaranteed by enabled condition");
3035
+ (0, import_ts_essentials6.assert)(publicClient, "Public client is guaranteed by enabled condition");
2859
3036
  try {
2860
- const receipt = await publicClient.waitForTransactionReceipt({ hash: tx.hash });
3037
+ const receipt = await publicClient.waitForTransactionReceipt({
3038
+ hash: tx.hash
3039
+ });
2861
3040
  const status = receipt.status === "success" ? "confirmed" /* Confirmed */ : "failed" /* Failed */;
2862
3041
  if (status === "confirmed" /* Confirmed */) onReceiptSuccess(tx, receipt);
2863
3042
  else onReceiptFail?.(tx, receipt);
@@ -2913,7 +3092,7 @@ function useHandleInvalidations() {
2913
3092
  });
2914
3093
  } else if (tx.actionType === "claim" /* Claim */) {
2915
3094
  const publicTokenAddress = tx.token.extensions.fhenix.erc20Pair?.address;
2916
- (0, import_ts_essentials5.assert)(publicTokenAddress, "Public pair token address is required for claim transaction invalidation");
3095
+ (0, import_ts_essentials6.assert)(publicTokenAddress, "Public pair token address is required for claim transaction invalidation");
2917
3096
  invalidatePublicTokenBalanceQueries(
2918
3097
  {
2919
3098
  tokenAddress: publicTokenAddress,
@@ -2927,6 +3106,20 @@ function useHandleInvalidations() {
2927
3106
  accountAddress: tx.account,
2928
3107
  queryClient
2929
3108
  });
3109
+ } else if (tx.actionType === "approve" /* Approve */) {
3110
+ const underlying = tx.token.extensions.fhenix.erc20Pair?.address;
3111
+ (0, import_ts_essentials6.assert)(underlying, "erc20Pair is required for approve transaction invalidation");
3112
+ if (underlying.toLowerCase() !== ETH_ADDRESS_LOWERCASE) {
3113
+ invalidateTokenAllowanceQueries(
3114
+ {
3115
+ chainId: tx.token.chainId,
3116
+ tokenAddress: underlying,
3117
+ ownerAddress: tx.account,
3118
+ spenderAddress: tx.token.address
3119
+ },
3120
+ queryClient
3121
+ );
3122
+ }
2930
3123
  } else {
2931
3124
  console.warn("No invalidation logic for transaction action type:", tx.actionType);
2932
3125
  }
@@ -2971,7 +3164,7 @@ function useTrackPendingTransactions() {
2971
3164
  import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo10, useRef as useRef2 } from "react";
2972
3165
 
2973
3166
  // src/hooks/useTransactionReceiptsByHash.ts
2974
- var import_ts_essentials6 = __toESM(require_dist(), 1);
3167
+ var import_ts_essentials7 = __toESM(require_dist(), 1);
2975
3168
  import { useMemo as useMemo8 } from "react";
2976
3169
  function useTransactionReceiptsByHash({ hashes, chainId: chainIdInput, enabled: enabledInput = true, queryOptions } = {
2977
3170
  enabled: true
@@ -2988,7 +3181,7 @@ function useTransactionReceiptsByHash({ hashes, chainId: chainIdInput, enabled:
2988
3181
  queryKey,
2989
3182
  enabled,
2990
3183
  queryFn: async () => {
2991
- (0, import_ts_essentials6.assert)(publicClient, "Public client is guaranteed by enabled condition");
3184
+ (0, import_ts_essentials7.assert)(publicClient, "Public client is guaranteed by enabled condition");
2992
3185
  return await publicClient.getTransactionReceipt({ hash });
2993
3186
  },
2994
3187
  notifyOnChangeProps: ["data"],
@@ -3015,7 +3208,7 @@ function useTransactionReceiptsByHash({ hashes, chainId: chainIdInput, enabled:
3015
3208
  }
3016
3209
 
3017
3210
  // src/hooks/useTrackDecryptingTransactions.ts
3018
- var import_ts_essentials7 = __toESM(require_dist());
3211
+ var import_ts_essentials8 = __toESM(require_dist());
3019
3212
 
3020
3213
  // src/hooks/useCofheReadDecryptionResults.ts
3021
3214
  import { useMemo as useMemo9 } from "react";
@@ -3154,7 +3347,7 @@ function useTrackDecryptingTransactionsBase({ onDecryptionResolve }) {
3154
3347
  const minedTxsWithCiphertextsToWatch = useMemo10(() => {
3155
3348
  return Object.entries(minedTxsPendingDecryptionByHash).filter(([, tx]) => receiptsByHash[tx.hash] !== void 0).map(([, tx]) => {
3156
3349
  const ciphertextToWatch = findDecryptRequestLog(tx.account, receiptsByHash[tx.hash].logs)?.ciphertext;
3157
- (0, import_ts_essentials7.assert)(
3350
+ (0, import_ts_essentials8.assert)(
3158
3351
  ciphertextToWatch,
3159
3352
  "Ciphertext to watch should be found in the transaction logs if a tx that isPendingDecryption and has a receipt"
3160
3353
  );
@@ -3174,7 +3367,7 @@ function useTrackDecryptingTransactionsBase({ onDecryptionResolve }) {
3174
3367
  useEffect2(() => {
3175
3368
  const txsWithDecryptionResults = minedTxsWithCiphertextsToWatch.filter(({ ciphertextToWatch }) => decryptionResults[ciphertextToWatch] !== void 0).map((item) => {
3176
3369
  const decryptionResult = decryptionResults[item.ciphertextToWatch];
3177
- (0, import_ts_essentials7.assert)(decryptionResult, "decryptionResult must be defined here");
3370
+ (0, import_ts_essentials8.assert)(decryptionResult, "decryptionResult must be defined here");
3178
3371
  return {
3179
3372
  ...item,
3180
3373
  decryptionResult
@@ -3269,7 +3462,7 @@ var useCofheClient = () => {
3269
3462
  import "@tanstack/react-query";
3270
3463
  import "viem";
3271
3464
  import "@cofhe/sdk";
3272
- var import_ts_essentials9 = __toESM(require_dist(), 1);
3465
+ var import_ts_essentials10 = __toESM(require_dist(), 1);
3273
3466
 
3274
3467
  // src/hooks/useCofheReadContractAndDecrypt.ts
3275
3468
  import "@tanstack/react-query";
@@ -3278,7 +3471,7 @@ import { FheTypes as FheTypes2 } from "@cofhe/sdk";
3278
3471
 
3279
3472
  // src/hooks/useCofheDecrypt.ts
3280
3473
  import { CofheError as CofheError2 } from "@cofhe/sdk";
3281
- var import_ts_essentials8 = __toESM(require_dist(), 1);
3474
+ var import_ts_essentials9 = __toESM(require_dist(), 1);
3282
3475
  function useCofheDecrypt({
3283
3476
  input,
3284
3477
  onPoll: onPoll2
@@ -3291,7 +3484,7 @@ function useCofheDecrypt({
3291
3484
  enabled,
3292
3485
  queryKey: ["decryptCiphertext", input?.ctHash.toString(), input?.utype],
3293
3486
  queryFn: async () => {
3294
- (0, import_ts_essentials8.assert)(input, "input is guaranteed to be defined by enabled condition");
3487
+ (0, import_ts_essentials9.assert)(input, "input is guaranteed to be defined by enabled condition");
3295
3488
  const builder = client.decryptForView(input.ctHash, input.utype);
3296
3489
  if (onPoll2) builder.onPoll(onPoll2);
3297
3490
  return builder.execute();
@@ -3382,7 +3575,7 @@ function useCofheTokenDecryptedBalance({ token, accountAddress, displayDecimals
3382
3575
  decryptingQueryOptions: {
3383
3576
  refetchOnMount: false,
3384
3577
  select: (amountWei) => {
3385
- (0, import_ts_essentials9.assert)(token, "Token must be defined to format confidential balance");
3578
+ (0, import_ts_essentials10.assert)(token, "Token must be defined to format confidential balance");
3386
3579
  if (typeof amountWei !== "bigint") {
3387
3580
  throw new Error("Expected bigint from confidential decryption");
3388
3581
  }
@@ -3408,7 +3601,7 @@ import "@cofhe/sdk";
3408
3601
  import { extractEncryptableValues, insertEncryptedValues } from "@cofhe/abi";
3409
3602
 
3410
3603
  // src/hooks/useCofheEncrypt.ts
3411
- var import_ts_essentials10 = __toESM(require_dist(), 1);
3604
+ var import_ts_essentials11 = __toESM(require_dist(), 1);
3412
3605
  import {
3413
3606
  assertCorrectEncryptedItemInput,
3414
3607
  isLastEncryptionStep
@@ -3508,7 +3701,7 @@ function useCofheEncrypt(options) {
3508
3701
  ...options,
3509
3702
  mutationKey: options?.mutationKey ?? ["cofhe", "encryptInputs"],
3510
3703
  mutationFn: async (variables) => {
3511
- (0, import_ts_essentials10.assert)(client, "CoFHE client not initialized");
3704
+ (0, import_ts_essentials11.assert)(client, "CoFHE client not initialized");
3512
3705
  const key = crypto.randomUUID();
3513
3706
  handleStepSetKey(key);
3514
3707
  const items = hasEncryptInputsOptions(variables) ? variables.items : variables;
@@ -3543,19 +3736,28 @@ function useCofheEncrypt(options) {
3543
3736
  }
3544
3737
 
3545
3738
  // src/hooks/useCofheWriteContract.ts
3546
- var import_ts_essentials11 = __toESM(require_dist(), 1);
3739
+ var import_ts_essentials12 = __toESM(require_dist(), 1);
3547
3740
  function hasExtras(variables) {
3548
3741
  return typeof variables === "object" && variables !== null && "writeContractInput" in variables && "extras" in variables;
3549
3742
  }
3550
3743
  function useCofheWriteContract(options) {
3551
3744
  const walletClient = useCofheWalletClient();
3745
+ const publicClient = useCofhePublicClient();
3552
3746
  const mutation = useInternalMutation({
3553
3747
  ...options,
3554
3748
  mutationKey: options?.mutationKey ?? ["cofhe", "walletWriteContract"],
3555
3749
  mutationFn: async (variables) => {
3556
- (0, import_ts_essentials11.assert)(walletClient, "WalletClient is required to write to a contract");
3750
+ (0, import_ts_essentials12.assert)(walletClient, "WalletClient is required to write to a contract");
3751
+ (0, import_ts_essentials12.assert)(publicClient, "PublicClient is required to simulate contract before writing");
3557
3752
  const writeContractInput = hasExtras(variables) ? variables.writeContractInput : variables;
3558
- return walletClient.writeContract(writeContractInput);
3753
+ const accountForSimulation = writeContractInput.account ?? walletClient.account;
3754
+ (0, import_ts_essentials12.assert)(accountForSimulation, "Wallet account is required to simulate contract before writing");
3755
+ const { chain, ...rest } = writeContractInput;
3756
+ const { request } = await publicClient.simulateContract({
3757
+ ...chain ? { ...rest, chain } : rest,
3758
+ account: accountForSimulation
3759
+ });
3760
+ return walletClient.writeContract({ ...request, chain: void 0 });
3559
3761
  }
3560
3762
  });
3561
3763
  const writeContractAsync = async (params) => mutation.mutateAsync(params);
@@ -3675,10 +3877,97 @@ function useCofheTokenTransfer(writeMutationOptions) {
3675
3877
  };
3676
3878
  }
3677
3879
 
3880
+ // src/hooks/useCofheTokenApprove.ts
3881
+ function useCofheTokenApprove(writeMutationOptions) {
3882
+ const { onSuccess: passedOnSuccess, onError: passedOnError, ...restOfOptions } = writeMutationOptions || {};
3883
+ const { onTransactionSubmitError } = useTransactionGlobalLifecycle();
3884
+ return useCofheWriteContract({
3885
+ ...restOfOptions,
3886
+ onError: (error, variables, onMutateResult, context) => {
3887
+ if (passedOnError) passedOnError(error, variables, onMutateResult, context);
3888
+ onTransactionSubmitError(error, "approve" /* Approve */);
3889
+ },
3890
+ onSuccess: (hash, variables, onMutateResult, context) => {
3891
+ if (passedOnSuccess) passedOnSuccess(hash, variables, onMutateResult, context);
3892
+ if (!hasExtras(variables)) return;
3893
+ const { token, tokenAmount, account } = variables.extras;
3894
+ useTransactionStore.getState().addTransaction({
3895
+ hash,
3896
+ token,
3897
+ tokenAmount,
3898
+ chainId: token.chainId,
3899
+ actionType: "approve" /* Approve */,
3900
+ isPendingDecryption: false,
3901
+ account
3902
+ });
3903
+ }
3904
+ });
3905
+ }
3906
+
3678
3907
  // src/hooks/useCofheTokenShield.ts
3679
3908
  import "@tanstack/react-query";
3680
3909
  import "viem";
3681
- var import_ts_essentials12 = __toESM(require_dist(), 1);
3910
+ var import_ts_essentials13 = __toESM(require_dist(), 1);
3911
+ function getCofheTokenShieldCallArgs(params) {
3912
+ const { token, amount, account } = params;
3913
+ const tokenAddress = token.address;
3914
+ const confidentialityType = token.extensions.fhenix.confidentialityType;
3915
+ if (!confidentialityType) {
3916
+ throw new Error("confidentialityType is required in token extensions");
3917
+ }
3918
+ if (confidentialityType !== "dual" && confidentialityType !== "wrapped") {
3919
+ throw new Error(`Shield not supported for confidentialityType: ${confidentialityType}`);
3920
+ }
3921
+ if (confidentialityType === "dual") {
3922
+ const contractConfig = SHIELD_ABIS.dual;
3923
+ return {
3924
+ main: {
3925
+ address: tokenAddress,
3926
+ abi: contractConfig.abi,
3927
+ functionName: contractConfig.functionName,
3928
+ args: [amount],
3929
+ account,
3930
+ chain: void 0
3931
+ }
3932
+ };
3933
+ }
3934
+ const erc20PairAddress = token.extensions.fhenix.erc20Pair?.address;
3935
+ const isEth = erc20PairAddress?.toLowerCase() === ETH_ADDRESS_LOWERCASE;
3936
+ if (isEth) {
3937
+ return {
3938
+ main: {
3939
+ address: tokenAddress,
3940
+ abi: WRAPPED_ETH_ENCRYPT_ETH_ABI,
3941
+ functionName: "encryptETH",
3942
+ args: [account],
3943
+ value: amount,
3944
+ account,
3945
+ chain: void 0
3946
+ }
3947
+ };
3948
+ }
3949
+ if (!erc20PairAddress) {
3950
+ throw new Error("erc20Pair address is required for wrapped ERC20 tokens");
3951
+ }
3952
+ return {
3953
+ approval: {
3954
+ address: erc20PairAddress,
3955
+ abi: ERC20_APPROVE_ABI,
3956
+ functionName: "approve",
3957
+ args: [tokenAddress, amount],
3958
+ account,
3959
+ chain: void 0
3960
+ },
3961
+ main: {
3962
+ address: tokenAddress,
3963
+ abi: WRAPPED_ENCRYPT_ABI,
3964
+ functionName: "encrypt",
3965
+ args: [account, amount],
3966
+ account,
3967
+ chain: void 0
3968
+ }
3969
+ };
3970
+ }
3682
3971
  function useCofheTokenShield(options) {
3683
3972
  const walletClient = useCofheWalletClient();
3684
3973
  const publicClient = useCofhePublicClient();
@@ -3691,6 +3980,9 @@ function useCofheTokenShield(options) {
3691
3980
  if (!walletClient) {
3692
3981
  throw new Error("WalletClient is required for token shield");
3693
3982
  }
3983
+ if (!publicClient) {
3984
+ throw new Error("PublicClient is required to simulate shield before writing");
3985
+ }
3694
3986
  const tokenAddress = input.token.address;
3695
3987
  const confidentialityType = input.token.extensions.fhenix.confidentialityType;
3696
3988
  if (!confidentialityType) {
@@ -3707,66 +3999,39 @@ function useCofheTokenShield(options) {
3707
3999
  const erc20PairAddress = input.token.extensions.fhenix.erc20Pair?.address;
3708
4000
  const isEth = erc20PairAddress?.toLowerCase() === ETH_ADDRESS_LOWERCASE;
3709
4001
  if (isEth) {
3710
- hash = await walletClient.writeContract({
4002
+ const { request } = await publicClient.simulateContract({
3711
4003
  address: tokenAddress,
3712
4004
  abi: WRAPPED_ETH_ENCRYPT_ETH_ABI,
3713
4005
  functionName: "encryptETH",
3714
4006
  args: [walletClient.account.address],
3715
4007
  value: input.amount,
3716
- account: walletClient.account,
3717
- chain: void 0
4008
+ account: walletClient.account
3718
4009
  });
4010
+ hash = await walletClient.writeContract({ ...request, chain: void 0 });
3719
4011
  } else {
3720
4012
  if (!erc20PairAddress) {
3721
4013
  throw new Error("erc20Pair address is required for wrapped ERC20 tokens");
3722
4014
  }
3723
- if (!publicClient) {
3724
- throw new Error("PublicClient is required for allowance check");
3725
- }
3726
- input.onStatusChange?.("Checking allowance...");
3727
- const currentAllowance = await publicClient.readContract({
3728
- address: erc20PairAddress,
3729
- abi: ERC20_ALLOWANCE_ABI,
3730
- functionName: "allowance",
3731
- args: [walletClient.account.address, tokenAddress]
3732
- });
3733
- if (currentAllowance < input.amount) {
3734
- input.onStatusChange?.("Approval required - please confirm in wallet...");
3735
- const approvalHash = await walletClient.writeContract({
3736
- address: erc20PairAddress,
3737
- abi: ERC20_APPROVE_ABI,
3738
- functionName: "approve",
3739
- args: [tokenAddress, input.amount],
3740
- account: walletClient.account,
3741
- chain: void 0
3742
- });
3743
- input.onStatusChange?.("Waiting for approval confirmation...");
3744
- await publicClient.waitForTransactionReceipt({ hash: approvalHash });
3745
- input.onStatusChange?.("Approved! Now shielding...");
3746
- }
3747
4015
  input.onStatusChange?.("Please confirm shield in wallet...");
3748
- hash = await walletClient.writeContract({
4016
+ const { request: encryptRequest } = await publicClient.simulateContract({
3749
4017
  address: tokenAddress,
3750
4018
  abi: WRAPPED_ENCRYPT_ABI,
3751
4019
  functionName: "encrypt",
3752
4020
  args: [walletClient.account.address, input.amount],
3753
- account: walletClient.account,
3754
- chain: void 0
4021
+ account: walletClient.account
3755
4022
  });
4023
+ hash = await walletClient.writeContract({ ...encryptRequest, chain: void 0 });
3756
4024
  }
3757
4025
  } else {
3758
- const contractConfig = SHIELD_ABIS[confidentialityType];
3759
- if (!contractConfig) {
3760
- throw new Error(`Unsupported confidentialityType for shield: ${confidentialityType}`);
3761
- }
3762
- hash = await walletClient.writeContract({
4026
+ const contractConfig = SHIELD_ABIS.dual;
4027
+ const { request } = await publicClient.simulateContract({
3763
4028
  address: tokenAddress,
3764
4029
  abi: contractConfig.abi,
3765
4030
  functionName: contractConfig.functionName,
3766
4031
  args: [input.amount],
3767
- account: walletClient.account,
3768
- chain: void 0
4032
+ account: walletClient.account
3769
4033
  });
4034
+ hash = await walletClient.writeContract({ ...request, chain: void 0 });
3770
4035
  }
3771
4036
  return hash;
3772
4037
  },
@@ -3775,8 +4040,8 @@ function useCofheTokenShield(options) {
3775
4040
  onTransactionSubmitError(error, "shield" /* Shield */);
3776
4041
  },
3777
4042
  onSuccess: async (hash, input, onMutateResult, context) => {
3778
- (0, import_ts_essentials12.assert)(chainId, "Chain ID is required for token shield");
3779
- (0, import_ts_essentials12.assert)(account, "Wallet account is required for token shield");
4043
+ (0, import_ts_essentials13.assert)(chainId, "Chain ID is required for token shield");
4044
+ (0, import_ts_essentials13.assert)(account, "Wallet account is required for token shield");
3780
4045
  if (onSuccess) await onSuccess(hash, input, onMutateResult, context);
3781
4046
  useTransactionStore.getState().addTransaction({
3782
4047
  hash,
@@ -3796,17 +4061,17 @@ function useCofheTokenShield(options) {
3796
4061
  // src/hooks/useCofheTokenUnshield.ts
3797
4062
  import "@tanstack/react-query";
3798
4063
  import "viem";
3799
- var import_ts_essentials14 = __toESM(require_dist(), 1);
4064
+ var import_ts_essentials15 = __toESM(require_dist(), 1);
3800
4065
 
3801
4066
  // src/hooks/useStoredTransaction.ts
3802
4067
  import { useMemo as useMemo12 } from "react";
3803
- var import_ts_essentials13 = __toESM(require_dist(), 1);
4068
+ var import_ts_essentials14 = __toESM(require_dist(), 1);
3804
4069
  function useStoredTransaction(txHash) {
3805
4070
  const filter3 = useMemo12(() => {
3806
4071
  return (tx) => tx.hash === txHash;
3807
4072
  }, [txHash]);
3808
4073
  const { filteredTxs } = useStoredTransactions({ filter: filter3 });
3809
- (0, import_ts_essentials13.assert)(filteredTxs.length <= 1, "There should be at most one transaction with a given hash");
4074
+ (0, import_ts_essentials14.assert)(filteredTxs.length <= 1, "There should be at most one transaction with a given hash");
3810
4075
  return filteredTxs[0];
3811
4076
  }
3812
4077
 
@@ -3889,8 +4154,43 @@ function useStoredTransactionDecryptionEffect({
3889
4154
  }
3890
4155
 
3891
4156
  // src/hooks/useCofheTokenUnshield.ts
4157
+ function getCofheTokenUnshieldCallArgs(params) {
4158
+ const { token, amount: rawAmount, account } = params;
4159
+ const tokenAddress = token.address;
4160
+ const confidentialityType = token.extensions.fhenix.confidentialityType;
4161
+ if (!confidentialityType) {
4162
+ throw new Error("confidentialityType is required in token extensions");
4163
+ }
4164
+ if (confidentialityType !== "dual" && confidentialityType !== "wrapped") {
4165
+ throw new Error(`Unshield not supported for confidentialityType: ${confidentialityType}`);
4166
+ }
4167
+ const contractConfig = UNSHIELD_ABIS[confidentialityType];
4168
+ if (!contractConfig) {
4169
+ throw new Error(`Unsupported confidentialityType for unshield: ${confidentialityType}`);
4170
+ }
4171
+ if (confidentialityType === "wrapped") {
4172
+ return {
4173
+ address: tokenAddress,
4174
+ abi: contractConfig.abi,
4175
+ functionName: contractConfig.functionName,
4176
+ args: [account, rawAmount],
4177
+ account,
4178
+ chain: void 0
4179
+ };
4180
+ }
4181
+ const amount = BigInt.asUintN(64, rawAmount);
4182
+ return {
4183
+ address: tokenAddress,
4184
+ abi: contractConfig.abi,
4185
+ functionName: contractConfig.functionName,
4186
+ args: [amount],
4187
+ account,
4188
+ chain: void 0
4189
+ };
4190
+ }
3892
4191
  function useCofheTokenUnshieldMutation(options) {
3893
4192
  const walletClient = useCofheWalletClient();
4193
+ const publicClient = useCofhePublicClient();
3894
4194
  const chainId = useCofheChainId();
3895
4195
  const account = useCofheAccount();
3896
4196
  const { onTransactionSubmitError } = useTransactionGlobalLifecycle();
@@ -3900,6 +4200,9 @@ function useCofheTokenUnshieldMutation(options) {
3900
4200
  if (!walletClient) {
3901
4201
  throw new Error("WalletClient is required for token unshield");
3902
4202
  }
4203
+ if (!publicClient) {
4204
+ throw new Error("PublicClient is required to simulate unshield before writing");
4205
+ }
3903
4206
  const tokenAddress = input.token.address;
3904
4207
  const confidentialityType = input.token.extensions.fhenix.confidentialityType;
3905
4208
  if (!confidentialityType) {
@@ -3918,24 +4221,24 @@ function useCofheTokenUnshieldMutation(options) {
3918
4221
  let hash;
3919
4222
  input.onStatusChange?.("Please confirm in wallet...");
3920
4223
  if (confidentialityType === "wrapped") {
3921
- hash = await walletClient.writeContract({
4224
+ const { request } = await publicClient.simulateContract({
3922
4225
  address: tokenAddress,
3923
4226
  abi: contractConfig.abi,
3924
4227
  functionName: contractConfig.functionName,
3925
4228
  args: [walletClient.account.address, input.amount],
3926
- account: walletClient.account,
3927
- chain: void 0
4229
+ account: walletClient.account
3928
4230
  });
4231
+ hash = await walletClient.writeContract({ ...request, chain: void 0 });
3929
4232
  } else {
3930
4233
  const amount = BigInt.asUintN(64, input.amount);
3931
- hash = await walletClient.writeContract({
4234
+ const { request } = await publicClient.simulateContract({
3932
4235
  address: tokenAddress,
3933
4236
  abi: contractConfig.abi,
3934
4237
  functionName: contractConfig.functionName,
3935
4238
  args: [amount],
3936
- account: walletClient.account,
3937
- chain: void 0
4239
+ account: walletClient.account
3938
4240
  });
4241
+ hash = await walletClient.writeContract({ ...request, chain: void 0 });
3939
4242
  }
3940
4243
  return hash;
3941
4244
  },
@@ -3944,8 +4247,8 @@ function useCofheTokenUnshieldMutation(options) {
3944
4247
  onTransactionSubmitError(error, "unshield" /* Unshield */);
3945
4248
  },
3946
4249
  onSuccess: async (hash, input, onMutateResult, context) => {
3947
- (0, import_ts_essentials14.assert)(chainId, "Chain ID is required for token unshield");
3948
- (0, import_ts_essentials14.assert)(account, "Wallet account is required for token unshield");
4250
+ (0, import_ts_essentials15.assert)(chainId, "Chain ID is required for token unshield");
4251
+ (0, import_ts_essentials15.assert)(account, "Wallet account is required for token unshield");
3949
4252
  if (onSuccess) await onSuccess(hash, input, onMutateResult, context);
3950
4253
  useTransactionStore.getState().addTransaction({
3951
4254
  hash,
@@ -3985,9 +4288,33 @@ function useCofheTokenUnshield(input) {
3985
4288
  // src/hooks/useCofheTokenClaimUnshielded.ts
3986
4289
  import "@tanstack/react-query";
3987
4290
  import "viem";
3988
- var import_ts_essentials15 = __toESM(require_dist(), 1);
4291
+ var import_ts_essentials16 = __toESM(require_dist(), 1);
4292
+ function getCofheTokenClaimUnshieldedCallArgs(params) {
4293
+ const { token, account } = params;
4294
+ const tokenAddress = token.address;
4295
+ const confidentialityType = token.extensions.fhenix.confidentialityType;
4296
+ if (!confidentialityType) {
4297
+ throw new Error("confidentialityType is required in token extensions");
4298
+ }
4299
+ if (confidentialityType !== "dual" && confidentialityType !== "wrapped") {
4300
+ throw new Error(`Claim not supported for confidentialityType: ${confidentialityType}`);
4301
+ }
4302
+ const contractConfig = CLAIM_ABIS[confidentialityType];
4303
+ if (!contractConfig) {
4304
+ throw new Error(`Unsupported confidentialityType for claim: ${confidentialityType}`);
4305
+ }
4306
+ return {
4307
+ address: tokenAddress,
4308
+ abi: contractConfig.abi,
4309
+ functionName: contractConfig.functionName,
4310
+ args: [],
4311
+ account,
4312
+ chain: void 0
4313
+ };
4314
+ }
3989
4315
  function useCofheTokenClaimUnshielded(options) {
3990
4316
  const walletClient = useCofheWalletClient();
4317
+ const publicClient = useCofhePublicClient();
3991
4318
  const chainId = useCofheChainId();
3992
4319
  const account = useCofheAccount();
3993
4320
  const { onSuccess, onError, ...restOfOptions } = options || {};
@@ -3997,6 +4324,9 @@ function useCofheTokenClaimUnshielded(options) {
3997
4324
  if (!walletClient) {
3998
4325
  throw new Error("WalletClient is required for claim");
3999
4326
  }
4327
+ if (!publicClient) {
4328
+ throw new Error("PublicClient is required to simulate claim before writing");
4329
+ }
4000
4330
  const tokenAddress = input.token.address;
4001
4331
  const confidentialityType = input.token.extensions.fhenix.confidentialityType;
4002
4332
  if (!confidentialityType) {
@@ -4012,23 +4342,22 @@ function useCofheTokenClaimUnshielded(options) {
4012
4342
  if (!contractConfig) {
4013
4343
  throw new Error(`Unsupported confidentialityType for claim: ${confidentialityType}`);
4014
4344
  }
4015
- const hash = await walletClient.writeContract({
4345
+ const { request } = await publicClient.simulateContract({
4016
4346
  address: tokenAddress,
4017
4347
  abi: contractConfig.abi,
4018
4348
  functionName: contractConfig.functionName,
4019
4349
  args: [],
4020
- account: walletClient.account,
4021
- chain: void 0
4350
+ account: walletClient.account
4022
4351
  });
4023
- return hash;
4352
+ return await walletClient.writeContract({ ...request, chain: void 0 });
4024
4353
  },
4025
4354
  onError: (error, variables, onMutateResult, context) => {
4026
4355
  if (onError) onError(error, variables, onMutateResult, context);
4027
4356
  onTransactionSubmitError(error, "claim" /* Claim */);
4028
4357
  },
4029
4358
  onSuccess: async (hash, input, onMutateResult, context) => {
4030
- (0, import_ts_essentials15.assert)(chainId, "Chain ID is required for claim");
4031
- (0, import_ts_essentials15.assert)(account, "Wallet account is required for claim");
4359
+ (0, import_ts_essentials16.assert)(chainId, "Chain ID is required for claim");
4360
+ (0, import_ts_essentials16.assert)(account, "Wallet account is required for claim");
4032
4361
  if (onSuccess) await onSuccess(hash, input, onMutateResult, context);
4033
4362
  useTransactionStore.getState().addTransaction({
4034
4363
  hash,
@@ -4046,7 +4375,7 @@ function useCofheTokenClaimUnshielded(options) {
4046
4375
  }
4047
4376
 
4048
4377
  // src/hooks/useCofheTokensClaimable.ts
4049
- var import_ts_essentials17 = __toESM(require_dist(), 1);
4378
+ var import_ts_essentials18 = __toESM(require_dist(), 1);
4050
4379
  import "@tanstack/react-query";
4051
4380
  import { useMemo as useMemo15 } from "react";
4052
4381
  import "viem";
@@ -4078,7 +4407,7 @@ function useNormalizedList(items, options) {
4078
4407
  }
4079
4408
 
4080
4409
  // src/hooks/useIsWaitingForDecryptionByAddress.ts
4081
- var import_ts_essentials16 = __toESM(require_dist(), 1);
4410
+ var import_ts_essentials17 = __toESM(require_dist(), 1);
4082
4411
  import { useMemo as useMemo14 } from "react";
4083
4412
  import { isAddress } from "viem";
4084
4413
  function useIsWaitingForDecryptionByAddress(entries) {
@@ -4086,7 +4415,7 @@ function useIsWaitingForDecryptionByAddress(entries) {
4086
4415
  return useMemo14(() => {
4087
4416
  const map = {};
4088
4417
  for (const entry of entries) {
4089
- (0, import_ts_essentials16.assert)(isAddress(entry.address), "address is valid");
4418
+ (0, import_ts_essentials17.assert)(isAddress(entry.address), "address is valid");
4090
4419
  map[entry.address] = awaitingDecryptionKeySet.has(JSON.stringify(entry.queryKey));
4091
4420
  }
4092
4421
  return map;
@@ -4163,12 +4492,12 @@ function useCofheTokensClaimable({ tokens, accountAddress: account }, queryOptio
4163
4492
  signal,
4164
4493
  queryKey: queryKey2
4165
4494
  }) => {
4166
- (0, import_ts_essentials17.assert)(
4495
+ (0, import_ts_essentials18.assert)(
4167
4496
  isTokenConfidentialityTypeClaimable(confidentialityType),
4168
4497
  "confidentialityType narrowed by token guard"
4169
4498
  );
4170
- (0, import_ts_essentials17.assert)(account, "account is required to fetch unshield claims");
4171
- (0, import_ts_essentials17.assert)(publicClient, "publicClient is required to fetch unshield claims");
4499
+ (0, import_ts_essentials18.assert)(account, "account is required to fetch unshield claims");
4500
+ (0, import_ts_essentials18.assert)(publicClient, "publicClient is required to fetch unshield claims");
4172
4501
  return fetchUnshieldClaimsSummary({
4173
4502
  publicClient,
4174
4503
  token,
@@ -4232,29 +4561,17 @@ function useCofheTokensClaimable({ tokens, accountAddress: account }, queryOptio
4232
4561
  import { useMemo as useMemo16 } from "react";
4233
4562
 
4234
4563
  // src/hooks/useCofheReadContracts.ts
4235
- var import_ts_essentials18 = __toESM(require_dist(), 1);
4564
+ var import_ts_essentials19 = __toESM(require_dist(), 1);
4236
4565
  import "@tanstack/react-query";
4237
4566
  import { transformEncryptedReturnTypes as transformEncryptedReturnTypes2 } from "@cofhe/abi";
4238
4567
  var QUERY_CACHE_PREFIX2 = "cofheReadContracts";
4239
- function serializeIfBigint2(value) {
4240
- if (typeof value === "bigint") {
4241
- return value.toString();
4242
- }
4243
- }
4244
- function serializeBigintRecursively2(value) {
4245
- if (Array.isArray(value)) {
4246
- return value.map(serializeBigintRecursively2);
4247
- } else {
4248
- return serializeIfBigint2(value);
4249
- }
4250
- }
4251
4568
  function constructCofheReadContractsQueryKey(params) {
4252
4569
  const { cofheChainId, contracts, allowFailure, enabled } = params;
4253
4570
  const serializedContracts = (contracts ?? []).map((contract) => {
4254
4571
  return {
4255
4572
  address: contract.address,
4256
4573
  functionName: contract.functionName,
4257
- args: Array.isArray(contract.args) ? serializeBigintRecursively2(contract.args) : contract.args,
4574
+ args: Array.isArray(contract.args) ? serializeBigintRecursively(contract.args) : contract.args,
4258
4575
  abi: contract.abi
4259
4576
  };
4260
4577
  });
@@ -4293,8 +4610,8 @@ function useCofheReadContracts(params, queryOptions) {
4293
4610
  enabled
4294
4611
  }),
4295
4612
  queryFn: async () => {
4296
- (0, import_ts_essentials18.assert)(publicClient, "PublicClient should be guaranteed by enabled check");
4297
- (0, import_ts_essentials18.assert)(contracts && contracts.length > 0, "Contracts should be guaranteed by enabled check");
4613
+ (0, import_ts_essentials19.assert)(publicClient, "PublicClient should be guaranteed by enabled check");
4614
+ (0, import_ts_essentials19.assert)(contracts && contracts.length > 0, "Contracts should be guaranteed by enabled check");
4298
4615
  const raw = await publicClient.multicall({
4299
4616
  contracts,
4300
4617
  ...multicallOptions || {}
@@ -4473,6 +4790,69 @@ function useTokensWithPublicBalances({ chainId: _chainId, accountAddress, displa
4473
4790
  };
4474
4791
  }
4475
4792
 
4793
+ // src/hooks/useCofheSimulateWriteContract.ts
4794
+ var import_ts_essentials20 = __toESM(require_dist(), 1);
4795
+ var QUERY_CACHE_PREFIX3 = "cofheSimulateWriteContract";
4796
+ function isRecord2(value) {
4797
+ return typeof value === "object" && value !== null;
4798
+ }
4799
+ function hasAddress(value) {
4800
+ return "address" in value && typeof value.address === "string";
4801
+ }
4802
+ function getAccountAddress(account) {
4803
+ if (!account) return void 0;
4804
+ if (typeof account === "string") return account;
4805
+ if (typeof account === "object" && hasAddress(account)) return account.address;
4806
+ return void 0;
4807
+ }
4808
+ function constructCofheSimulateWriteContractQueryKey(params) {
4809
+ const { cofheChainId, callArgs, resolvedAccountAddress, enabled } = params;
4810
+ const serialized = isRecord2(callArgs) ? {
4811
+ address: typeof callArgs.address === "string" ? callArgs.address : void 0,
4812
+ functionName: typeof callArgs.functionName === "string" ? callArgs.functionName : void 0,
4813
+ args: Array.isArray(callArgs.args) ? serializeBigintRecursively(callArgs.args) : void 0,
4814
+ value: serializeIfBigint(callArgs.value),
4815
+ dataSuffix: typeof callArgs.dataSuffix === "string" ? callArgs.dataSuffix : void 0,
4816
+ account: resolvedAccountAddress
4817
+ } : void 0;
4818
+ return [QUERY_CACHE_PREFIX3, cofheChainId, serialized, enabled];
4819
+ }
4820
+ function useCofheSimulateWriteContract(callArgs, queryOptions) {
4821
+ const walletClient = useCofheWalletClient();
4822
+ const publicClient = useCofhePublicClient();
4823
+ const cofheChainId = useCofheChainId();
4824
+ const isCofheErrorActive = useIsCofheErrorActive();
4825
+ const { enabled: userEnabled, ...restQueryOptions } = queryOptions || {};
4826
+ const resolvedAccountAddress = getAccountAddress(callArgs?.account ?? walletClient?.account);
4827
+ const enabled = !isCofheErrorActive && !!publicClient && !!walletClient && !!callArgs && !!resolvedAccountAddress && (userEnabled ?? true);
4828
+ return useInternalQuery({
4829
+ enabled,
4830
+ queryKey: constructCofheSimulateWriteContractQueryKey({
4831
+ cofheChainId,
4832
+ callArgs,
4833
+ resolvedAccountAddress,
4834
+ enabled
4835
+ }),
4836
+ retry: false,
4837
+ // no need to retry on error. The whole point of simulation is to check if there's an error
4838
+ queryFn: async () => {
4839
+ (0, import_ts_essentials20.assert)(publicClient, "PublicClient is required to simulate contract");
4840
+ (0, import_ts_essentials20.assert)(walletClient, "WalletClient is required to simulate contract");
4841
+ (0, import_ts_essentials20.assert)(callArgs, "callArgs is guaranteed to be defined by enabled condition");
4842
+ const accountForSimulation = callArgs.account ?? walletClient.account;
4843
+ (0, import_ts_essentials20.assert)(accountForSimulation, "Wallet account is required to simulate contract");
4844
+ const simulateArgs = {
4845
+ ...callArgs,
4846
+ account: accountForSimulation
4847
+ };
4848
+ return publicClient.simulateContract(
4849
+ simulateArgs
4850
+ );
4851
+ },
4852
+ ...restQueryOptions
4853
+ });
4854
+ }
4855
+
4476
4856
  // src/hooks/permits/usePermitDuration.ts
4477
4857
  import { useState as useState3, useCallback as useCallback4, useMemo as useMemo18 } from "react";
4478
4858
  function usePermitDuration(options) {
@@ -5744,7 +6124,7 @@ var StatusBarContent = () => {
5744
6124
  };
5745
6125
 
5746
6126
  // src/components/FnxFloatingButton/ContentSection.tsx
5747
- import { useEffect as useEffect12, useMemo as useMemo32, useRef as useRef8 } from "react";
6127
+ import { useEffect as useEffect12, useMemo as useMemo33, useRef as useRef8 } from "react";
5748
6128
 
5749
6129
  // src/components/FnxFloatingButton/pages/MainPage/WalletHeader.tsx
5750
6130
  import { MdOutlineAccountBalanceWallet } from "react-icons/md";
@@ -6090,7 +6470,7 @@ import { IoMdKey } from "react-icons/io";
6090
6470
  import { AiOutlinePieChart } from "react-icons/ai";
6091
6471
  import { TbShieldPlus as TbShieldPlus2 } from "react-icons/tb";
6092
6472
  import { FaBug } from "react-icons/fa";
6093
- var import_ts_essentials19 = __toESM(require_dist(), 1);
6473
+ var import_ts_essentials21 = __toESM(require_dist(), 1);
6094
6474
 
6095
6475
  // src/components/FnxFloatingButton/components/ActionButton.tsx
6096
6476
  import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -6379,6 +6759,8 @@ var ActionIcon = ({ actionType }) => {
6379
6759
  return /* @__PURE__ */ jsx27(TbShieldMinus, { className: iconClassName2 });
6380
6760
  case "claim" /* Claim */:
6381
6761
  return /* @__PURE__ */ jsx27(TbShieldCheck, { className: iconClassName2 });
6762
+ case "approve" /* Approve */:
6763
+ return /* @__PURE__ */ jsx27(GoArrowUpRight, { className: iconClassName2 });
6382
6764
  default:
6383
6765
  return /* @__PURE__ */ jsx27(GoArrowUpRight, { className: iconClassName2 });
6384
6766
  }
@@ -6475,12 +6857,12 @@ var BottomNavigation = () => {
6475
6857
  return;
6476
6858
  }
6477
6859
  if (page === "send" /* Send */) {
6478
- (0, import_ts_essentials19.assert)(defaultToken, "No pinned token available for Send page");
6860
+ (0, import_ts_essentials21.assert)(defaultToken, "No pinned token available for Send page");
6479
6861
  navigateTo("send" /* Send */, { pageProps: { token: defaultToken } });
6480
6862
  return;
6481
6863
  }
6482
6864
  if (page === "shield" /* Shield */) {
6483
- (0, import_ts_essentials19.assert)(defaultToken, "No pinned token available for Shield page");
6865
+ (0, import_ts_essentials21.assert)(defaultToken, "No pinned token available for Shield page");
6484
6866
  navigateTo("shield" /* Shield */, { pageProps: { token: defaultToken } });
6485
6867
  return;
6486
6868
  }
@@ -7621,7 +8003,7 @@ import { useState as useState10, useMemo as useMemo30 } from "react";
7621
8003
  import ArrowBackIcon7 from "@mui/icons-material/ArrowBack";
7622
8004
  import KeyboardArrowRightIcon2 from "@mui/icons-material/KeyboardArrowRight";
7623
8005
  import { isAddress as isAddress2, maxUint128 } from "viem";
7624
- var import_ts_essentials20 = __toESM(require_dist(), 1);
8006
+ var import_ts_essentials22 = __toESM(require_dist(), 1);
7625
8007
  import { Fragment as Fragment9, jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
7626
8008
  var SendPage = ({ token: _token }) => {
7627
8009
  const [overriddenToken, setOverriddenToken] = useState10(null);
@@ -7640,7 +8022,7 @@ var SendPage = ({ token: _token }) => {
7640
8022
  }
7641
8023
  } = useCofheTokenTransfer({
7642
8024
  onError: (error2) => {
7643
- const errorMessage = error2 instanceof Error ? error2.message : "Failed to send tokens";
8025
+ const errorMessage = cofheHumanizeViemError(error2) ?? (error2 instanceof Error ? error2.message : "Failed to send tokens");
7644
8026
  setError(errorMessage);
7645
8027
  console.error("Send tx submit error:", error2);
7646
8028
  },
@@ -7676,10 +8058,10 @@ var SendPage = ({ token: _token }) => {
7676
8058
  const isValidAddress2 = isAddress2(recipientAddress);
7677
8059
  const isValidAmount = (amount.length > 0 && confidentialUnitBalance && confidentialUnitBalance.gte(amount)) ?? false;
7678
8060
  const handleSend = async () => {
7679
- (0, import_ts_essentials20.assert)(isAddress2(recipientAddress), "Recipient address is not valid");
8061
+ (0, import_ts_essentials22.assert)(isAddress2(recipientAddress), "Recipient address is not valid");
7680
8062
  const amountWei = unitToWei(amount, token.decimals);
7681
- (0, import_ts_essentials20.assert)(amountWei <= maxUint128, "Amount exceeds maximum supported value (uint128 max)");
7682
- (0, import_ts_essentials20.assert)(account, "Sender account is required");
8063
+ (0, import_ts_essentials22.assert)(amountWei <= maxUint128, "Amount exceeds maximum supported value (uint128 max)");
8064
+ (0, import_ts_essentials22.assert)(account, "Sender account is required");
7683
8065
  await encryptAmountAndSendToken({
7684
8066
  input: {
7685
8067
  token,
@@ -7693,6 +8075,7 @@ var SendPage = ({ token: _token }) => {
7693
8075
  if (confidentialUnitBalance) setAmount(confidentialUnitBalance.toFixed());
7694
8076
  };
7695
8077
  const { tokensWithExistingEncryptedBalance } = useCofheTokensWithExistingEncryptedBalances();
8078
+ const disabled = !isValidAddress2 || !isValidAmount || isSending || isEncryptingInput;
7696
8079
  return /* @__PURE__ */ jsx48(
7697
8080
  PageContainer,
7698
8081
  {
@@ -7799,11 +8182,11 @@ var SendPage = ({ token: _token }) => {
7799
8182
  "button",
7800
8183
  {
7801
8184
  onClick: handleSend,
7802
- disabled: !isValidAddress2 || !isValidAmount || isSending || isEncryptingInput,
8185
+ disabled,
7803
8186
  className: cn(
7804
8187
  "fnx-send-button w-full py-3 px-4 font-small",
7805
8188
  "flex items-center justify-center gap-2",
7806
- isValidAddress2 && isValidAmount && !isSending && !isEncryptingInput ? "fnx-send-button-enabled" : "fnx-send-button-disabled"
8189
+ disabled ? "fnx-send-button-disabled" : "fnx-send-button-enabled"
7807
8190
  ),
7808
8191
  children: [
7809
8192
  /* @__PURE__ */ jsx48("span", { children: "Send" }),
@@ -7820,9 +8203,9 @@ import ArrowBackIcon8 from "@mui/icons-material/ArrowBack";
7820
8203
  import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
7821
8204
  import { TbShieldPlus as TbShieldPlus3, TbShieldMinus as TbShieldMinus2 } from "react-icons/tb";
7822
8205
  import { LuExternalLink as LuExternalLink2 } from "react-icons/lu";
7823
- import { useState as useState11 } from "react";
7824
- import { parseUnits } from "viem";
7825
- var import_ts_essentials21 = __toESM(require_dist(), 1);
8206
+ import { useMemo as useMemo31, useState as useState11 } from "react";
8207
+ import { ContractFunctionExecutionError as ContractFunctionExecutionError3, parseUnits } from "viem";
8208
+ var import_ts_essentials23 = __toESM(require_dist(), 1);
7826
8209
 
7827
8210
  // src/hooks/useIsUnshieldingMining.ts
7828
8211
  import { useCallback as useCallback13 } from "react";
@@ -7856,6 +8239,13 @@ function useLifecycleStore() {
7856
8239
  setStatus
7857
8240
  };
7858
8241
  }
8242
+ function humanizeSimulationError(error, fallbackMessage) {
8243
+ if (!error) return null;
8244
+ const cofheErrorMessage = cofheHumanizeViemError(error);
8245
+ if (cofheErrorMessage) return cofheErrorMessage;
8246
+ if (error instanceof ContractFunctionExecutionError3) return error.shortMessage;
8247
+ return fallbackMessage;
8248
+ }
7859
8249
  function TxHashWithActions({ hash, chainId }) {
7860
8250
  const href = chainId ? getBlockExplorerTxUrl(chainId, hash) : void 0;
7861
8251
  const ellipsed = truncateHash(hash);
@@ -7904,7 +8294,7 @@ function useClaimUnshieldedWithLifecycle() {
7904
8294
  });
7905
8295
  },
7906
8296
  onError: (error2) => {
7907
- const errorMessage = error2 instanceof Error ? error2.message : "Failed to claim unshielded tokens";
8297
+ const errorMessage = cofheHumanizeViemError(error2) ?? (error2 instanceof Error ? error2.message : "Failed to claim unshielded tokens");
7908
8298
  setError(errorMessage);
7909
8299
  setStatus(null);
7910
8300
  console.error("Claim tx submit error:", error2);
@@ -7963,12 +8353,18 @@ function useShieldWithLifecycle(token) {
7963
8353
  setShieldAmount("");
7964
8354
  },
7965
8355
  onError: (error2) => {
7966
- const errorMessage = error2 instanceof Error ? error2.message : "Failed to shield tokens";
8356
+ const errorMessage = cofheHumanizeViemError(error2) ?? (error2 instanceof Error ? error2.message : "Failed to shield tokens");
7967
8357
  setError(errorMessage);
7968
8358
  setStatus(null);
7969
8359
  console.error("Shield tx submit error:", error2);
7970
8360
  }
7971
8361
  });
8362
+ const { tokenApprove, isApprovingMining } = useApproveWithLifecycle({
8363
+ chainId,
8364
+ setError,
8365
+ setStatus,
8366
+ scheduleStatusClear
8367
+ });
7972
8368
  const { isMining: isTokenShieldMining } = useOnceTransactionMined({
7973
8369
  txHash: tokenShield.data,
7974
8370
  onceMined: (transaction) => {
@@ -8000,6 +8396,15 @@ function useShieldWithLifecycle(token) {
8000
8396
  onStatusChange: (message) => setStatus({ message, type: "info" })
8001
8397
  });
8002
8398
  };
8399
+ const handleApprove = async () => {
8400
+ (0, import_ts_essentials23.assert)(approvalCallArgs, "Approval call args are required to approve");
8401
+ (0, import_ts_essentials23.assert)(shieldAmountWei, "Shield amount is required to approve");
8402
+ (0, import_ts_essentials23.assert)(account, "Wallet account is required to approve");
8403
+ await tokenApprove.writeContractAsync({
8404
+ writeContractInput: { ...approvalCallArgs, account: account ?? null, chain: void 0 },
8405
+ extras: { token, tokenAmount: shieldAmountWei, account }
8406
+ });
8407
+ };
8003
8408
  const { data: { unit: publicBalanceUnit } = {}, isFetching: isFetchingPublic } = useCofheTokenPublicBalance({
8004
8409
  token,
8005
8410
  accountAddress: account
@@ -8009,20 +8414,66 @@ function useShieldWithLifecycle(token) {
8009
8414
  if (publicBalanceUnit) setShieldAmount(publicBalanceUnit.toFixed());
8010
8415
  };
8011
8416
  const isValidShieldAmount = (shieldAmount.length > 0 && publicBalanceUnit?.gte(shieldAmount)) ?? false;
8417
+ const shieldAmountWei = useMemo31(() => {
8418
+ if (!isValidShieldAmount) return void 0;
8419
+ try {
8420
+ return unitToWei(shieldAmount, token.decimals);
8421
+ } catch {
8422
+ return void 0;
8423
+ }
8424
+ }, [isValidShieldAmount, shieldAmount, token.decimals]);
8425
+ const shieldTxCallArgs = useMemo31(() => {
8426
+ if (!account || !shieldAmountWei) return void 0;
8427
+ return getCofheTokenShieldCallArgs({ token, amount: shieldAmountWei, account });
8428
+ }, [account, shieldAmountWei, token]);
8429
+ const shieldCallArgs = shieldTxCallArgs?.main;
8430
+ const approvalCallArgs = shieldTxCallArgs?.approval;
8431
+ const approvalSpender = approvalCallArgs?.args?.[0] ?? void 0;
8432
+ const { data: allowanceWei, isFetching: isFetchingAllowance } = useTokenAllowance(
8433
+ {
8434
+ tokenAddress: approvalCallArgs?.address,
8435
+ ownerAddress: account,
8436
+ spenderAddress: approvalSpender
8437
+ },
8438
+ {
8439
+ enabled: !!approvalCallArgs && !!account && !!approvalSpender && !!shieldAmountWei
8440
+ }
8441
+ );
8442
+ const requiresApproval = !!approvalCallArgs;
8443
+ const isAllowanceKnown = !requiresApproval || typeof allowanceWei === "bigint";
8444
+ const hasSufficientAllowance = !requiresApproval || typeof allowanceWei === "bigint" && typeof shieldAmountWei === "bigint" && allowanceWei >= shieldAmountWei;
8445
+ const shouldApprove = requiresApproval && typeof allowanceWei === "bigint" && typeof shieldAmountWei === "bigint" && allowanceWei < shieldAmountWei;
8446
+ const approvalSimulation = useCofheSimulateWriteContract(approvalCallArgs, {
8447
+ enabled: shouldApprove
8448
+ });
8449
+ const shieldSimulation = useCofheSimulateWriteContract(shieldCallArgs, {
8450
+ enabled: !!shieldCallArgs && !shouldApprove
8451
+ });
8452
+ const simulationError = useMemo31(() => {
8453
+ const relevantError = shouldApprove ? approvalSimulation.error : shieldSimulation.error;
8454
+ if (!relevantError) return null;
8455
+ const cofheErrorMessage = cofheHumanizeViemError(relevantError);
8456
+ if (cofheErrorMessage) return cofheErrorMessage;
8457
+ if (relevantError instanceof ContractFunctionExecutionError3) return relevantError.shortMessage;
8458
+ return shouldApprove ? "Failed to simulate approval transaction" : "Failed to simulate shield transaction";
8459
+ }, [approvalSimulation.error, shieldSimulation.error, shouldApprove]);
8460
+ const canShield = isValidShieldAmount && !!shieldCallArgs && isAllowanceKnown && hasSufficientAllowance && !shieldSimulation.isFetching && !shieldSimulation.error;
8461
+ const canApprove = isValidShieldAmount && shouldApprove && !isFetchingAllowance && !approvalSimulation.isFetching && !approvalSimulation.error;
8462
+ const canWrite = shouldApprove ? canApprove : canShield;
8012
8463
  return {
8013
8464
  status,
8014
- error,
8015
- isProcessing: tokenShield.isPending || isTokenShieldMining,
8465
+ error: error ?? simulationError,
8466
+ isProcessing: tokenShield.isPending || isTokenShieldMining || tokenApprove.isPending || isApprovingMining,
8016
8467
  inputAmount: shieldAmount,
8017
8468
  setInputAmount: setShieldAmount,
8018
8469
  onMaxClick: handleShieldMax,
8019
- isValidAmount: isValidShieldAmount,
8470
+ canWriteContract: canWrite,
8020
8471
  sourceSymbol: token.extensions.fhenix.erc20Pair?.symbol,
8021
8472
  destSymbol: token.symbol,
8022
8473
  sourceLogoURI: token.extensions.fhenix.erc20Pair?.logoURI,
8023
8474
  destLogoURI: token.logoURI,
8024
- handlePrimaryAction: handleShield,
8025
- primaryLabel: "Shield",
8475
+ handlePrimaryAction: shouldApprove ? handleApprove : handleShield,
8476
+ primaryLabel: shouldApprove ? "Approve" : "Shield",
8026
8477
  primaryIcon: /* @__PURE__ */ jsx49(TbShieldPlus3, { className: "w-3 h-3" })
8027
8478
  };
8028
8479
  }
@@ -8051,7 +8502,7 @@ function useUnshieldWithLifecycle(token) {
8051
8502
  setUnshieldAmount("");
8052
8503
  },
8053
8504
  onTransactionSubmitError: (error2) => {
8054
- const errorMessage = error2 instanceof Error ? error2.message : "Failed to unshield tokens";
8505
+ const errorMessage = cofheHumanizeViemError(error2) ?? (error2 instanceof Error ? error2.message : "Failed to unshield tokens");
8055
8506
  setError(errorMessage);
8056
8507
  setStatus(null);
8057
8508
  console.error("Unshield tx submit error:", error2);
@@ -8082,10 +8533,10 @@ function useUnshieldWithLifecycle(token) {
8082
8533
  }
8083
8534
  });
8084
8535
  const handleUnshield = async () => {
8085
- const amountInSmallestUnit = parseUnits(unshieldAmount, token.decimals);
8536
+ const amountWei = parseUnits(unshieldAmount, token.decimals);
8086
8537
  tokenUnshield.mutateAsync({
8087
8538
  token,
8088
- amount: amountInSmallestUnit,
8539
+ amount: amountWei,
8089
8540
  onStatusChange: (message) => setStatus({ message, type: "info" })
8090
8541
  });
8091
8542
  };
@@ -8094,14 +8545,27 @@ function useUnshieldWithLifecycle(token) {
8094
8545
  if (confidentialBalanceUnit) setUnshieldAmount(confidentialBalanceUnit.toFixed());
8095
8546
  };
8096
8547
  const isValidUnshieldAmount = (unshieldAmount.length > 0 && confidentialBalanceUnit?.gte(unshieldAmount)) ?? false;
8548
+ const unshieldCallArgs = useMemo31(() => {
8549
+ if (!account || !isValidUnshieldAmount) return void 0;
8550
+ const amountWei = parseUnits(unshieldAmount, token.decimals);
8551
+ return getCofheTokenUnshieldCallArgs({ token, amount: amountWei, account });
8552
+ }, [account, isValidUnshieldAmount, token, unshieldAmount]);
8553
+ const unshieldSimulation = useCofheSimulateWriteContract(unshieldCallArgs, {
8554
+ enabled: !!unshieldCallArgs
8555
+ });
8556
+ const simulationError = useMemo31(
8557
+ () => humanizeSimulationError(unshieldSimulation.error, "Failed to simulate unshield transaction"),
8558
+ [unshieldSimulation.error]
8559
+ );
8560
+ const canUnshield = isValidUnshieldAmount && !!unshieldCallArgs && !unshieldSimulation.isFetching && !unshieldSimulation.error;
8097
8561
  return {
8098
8562
  status,
8099
- error,
8563
+ error: error ?? simulationError,
8100
8564
  isProcessing: tokenUnshield.isPending || tokenUnshield.isTokenUnshieldMining || tokenUnshield.isPendingDecryption,
8101
8565
  inputAmount: unshieldAmount,
8102
8566
  setInputAmount: setUnshieldAmount,
8103
8567
  onMaxClick: handleUnshieldMax,
8104
- isValidAmount: isValidUnshieldAmount ?? false,
8568
+ canWriteContract: canUnshield,
8105
8569
  sourceSymbol: token.symbol,
8106
8570
  destSymbol: token.extensions.fhenix.erc20Pair?.symbol,
8107
8571
  sourceLogoURI: token.logoURI,
@@ -8154,19 +8618,30 @@ function ClaimingSection({ token }) {
8154
8618
  } = useClaimUnshieldedWithLifecycle();
8155
8619
  const pairedSymbol = token.extensions.fhenix.erc20Pair?.symbol;
8156
8620
  const handleClaim = async () => {
8157
- (0, import_ts_essentials21.assert)(unshieldedClaims, "Unshield claims data is required to claim unshielded tokens");
8621
+ (0, import_ts_essentials23.assert)(unshieldedClaims, "Unshield claims data is required to claim unshielded tokens");
8158
8622
  claimUnshield.mutateAsync({
8159
8623
  token,
8160
8624
  amount: unshieldedClaims.claimableAmount
8161
8625
  });
8162
8626
  };
8163
8627
  const isUnshieldingMining = useIsUnshieldingMining(token);
8628
+ const claimCallArgs = useMemo31(() => {
8629
+ if (!account || !unshieldedClaims?.hasClaimable) return void 0;
8630
+ return getCofheTokenClaimUnshieldedCallArgs({ token, account });
8631
+ }, [account, token, unshieldedClaims?.hasClaimable]);
8632
+ const claimSimulation = useCofheSimulateWriteContract(claimCallArgs, {
8633
+ enabled: !!claimCallArgs
8634
+ });
8635
+ const claimSimulationError = useMemo31(
8636
+ () => humanizeSimulationError(claimSimulation.error, "Failed to simulate claim transaction"),
8637
+ [claimSimulation.error]
8638
+ );
8164
8639
  return /* @__PURE__ */ jsxs34(Fragment10, { children: [
8165
8640
  unshieldedClaims?.hasClaimable && /* @__PURE__ */ jsx49(
8166
8641
  ActionButton,
8167
8642
  {
8168
8643
  onClick: handleClaim,
8169
- disabled: claimUnshield.isPending || isClaimingMining || isFetchingClaims || isWaitingForNewClaimsDecryption || isUnshieldingMining,
8644
+ disabled: claimUnshield.isPending || isClaimingMining || isFetchingClaims || isWaitingForNewClaimsDecryption || isUnshieldingMining || !claimCallArgs || claimSimulation.isFetching || !!claimSimulation.error,
8170
8645
  label: claimUnshield.isPending ? "Claiming..." : `Claim ${isFetchingClaims || isWaitingForNewClaimsDecryption ? "..." : formatTokenAmount(unshieldedClaims.claimableAmount, token.decimals, 5).formatted} ${pairedSymbol}`,
8171
8646
  className: "mt-1"
8172
8647
  }
@@ -8177,7 +8652,7 @@ function ClaimingSection({ token }) {
8177
8652
  " ",
8178
8653
  pairedSymbol
8179
8654
  ] }),
8180
- /* @__PURE__ */ jsx49(StatusAndError, { status: claimingStatus, error: claimingError })
8655
+ /* @__PURE__ */ jsx49(StatusAndError, { status: claimingStatus, error: claimingError ?? claimSimulationError })
8181
8656
  ] });
8182
8657
  }
8183
8658
  function StatusAndError({ status, error }) {
@@ -8215,7 +8690,7 @@ var ShieldAndUnshieldPageView = ({
8215
8690
  inputAmount,
8216
8691
  setInputAmount,
8217
8692
  onMaxClick,
8218
- isValidAmount,
8693
+ canWriteContract,
8219
8694
  sourceSymbol,
8220
8695
  destSymbol,
8221
8696
  sourceLogoURI,
@@ -8332,7 +8807,7 @@ var ShieldAndUnshieldPageView = ({
8332
8807
  ActionButton,
8333
8808
  {
8334
8809
  onClick: handlePrimaryAction,
8335
- disabled: !isValidAmount || isProcessing || !isShieldableToken,
8810
+ disabled: !canWriteContract || isProcessing || !isShieldableToken,
8336
8811
  icon: primaryIcon,
8337
8812
  label: primaryLabel,
8338
8813
  className: "py-2"
@@ -8345,6 +8820,62 @@ var ShieldAndUnshieldPageView = ({
8345
8820
  }
8346
8821
  );
8347
8822
  };
8823
+ function useApproveWithLifecycle({
8824
+ chainId,
8825
+ setError,
8826
+ setStatus,
8827
+ scheduleStatusClear
8828
+ }) {
8829
+ const tokenApprove = useCofheTokenApprove({
8830
+ onMutate: () => {
8831
+ setError(null);
8832
+ setStatus({ message: "Preparing approval transaction...", type: "info" });
8833
+ },
8834
+ onSuccess: (hash, variables) => {
8835
+ setStatus({
8836
+ message: /* @__PURE__ */ jsxs34(Fragment10, { children: [
8837
+ "Approval transaction sent! Hash: ",
8838
+ /* @__PURE__ */ jsx49(TxHashWithActions, { hash, chainId }),
8839
+ " Waiting for confirmation..."
8840
+ ] }),
8841
+ type: "info"
8842
+ });
8843
+ },
8844
+ onError: (error) => {
8845
+ const errorMessage = cofheHumanizeViemError(error) ?? (error instanceof Error ? error.message : "Failed to approve tokens");
8846
+ setError(errorMessage);
8847
+ setStatus(null);
8848
+ console.error("Approve tx submit error:", error);
8849
+ }
8850
+ });
8851
+ const { isMining: isApprovingMining } = useOnceTransactionMined({
8852
+ txHash: tokenApprove.data,
8853
+ onceMined: async (transaction) => {
8854
+ if (transaction.status === "confirmed") {
8855
+ setStatus({
8856
+ message: /* @__PURE__ */ jsxs34(Fragment10, { children: [
8857
+ "Approval transaction confirmed! Hash: ",
8858
+ /* @__PURE__ */ jsx49(TxHashWithActions, { hash: transaction.hash, chainId })
8859
+ ] }),
8860
+ type: "success"
8861
+ });
8862
+ } else if (transaction.status === "failed") {
8863
+ setError(
8864
+ /* @__PURE__ */ jsxs34(Fragment10, { children: [
8865
+ "Approval transaction failed! Hash: ",
8866
+ /* @__PURE__ */ jsx49(TxHashWithActions, { hash: transaction.hash, chainId })
8867
+ ] })
8868
+ );
8869
+ setStatus(null);
8870
+ }
8871
+ scheduleStatusClear();
8872
+ }
8873
+ });
8874
+ return {
8875
+ tokenApprove,
8876
+ isApprovingMining
8877
+ };
8878
+ }
8348
8879
 
8349
8880
  // src/components/FnxFloatingButton/pages/ActivityPage.tsx
8350
8881
  import ArrowBackIcon9 from "@mui/icons-material/ArrowBack";
@@ -8969,8 +9500,8 @@ var PortfolioPage = () => {
8969
9500
 
8970
9501
  // src/components/FnxFloatingButton/pages/ClaimableTokens.tsx
8971
9502
  import ArrowBackIcon13 from "@mui/icons-material/ArrowBack";
8972
- import { useMemo as useMemo31 } from "react";
8973
- var import_ts_essentials22 = __toESM(require_dist(), 1);
9503
+ import { useMemo as useMemo32 } from "react";
9504
+ var import_ts_essentials24 = __toESM(require_dist(), 1);
8974
9505
  import { jsx as jsx55, jsxs as jsxs40 } from "react/jsx-runtime";
8975
9506
  function ClaimableTokens() {
8976
9507
  const chainsClaimableTokens = useCofheClaimableTokens();
@@ -8982,7 +9513,7 @@ function ClaimableTokens() {
8982
9513
  const chainId = useCofheChainId();
8983
9514
  const allTokens = useCofheTokens(chainId);
8984
9515
  const claimMutation = useCofheTokenClaimUnshielded();
8985
- const rows = useMemo31(() => {
9516
+ const rows = useMemo32(() => {
8986
9517
  const byLower = new Map(allTokens.map((t) => [t.address.toLowerCase(), t]));
8987
9518
  return Object.entries(claimableByTokenAddress).filter(([_t, amount]) => amount > 0n).map(([tokenAddress, claimableAmount]) => {
8988
9519
  const token = byLower.get(tokenAddress.toLowerCase());
@@ -8991,7 +9522,7 @@ function ClaimableTokens() {
8991
9522
  }, [allTokens, claimableByTokenAddress]);
8992
9523
  const handleClaim = async (tokenAddress) => {
8993
9524
  const row = rows.find((r) => r.token.address.toLowerCase() === tokenAddress.toLowerCase());
8994
- (0, import_ts_essentials22.assert)(row, "token not found for claim");
9525
+ (0, import_ts_essentials24.assert)(row, "token not found for claim");
8995
9526
  await claimMutation.mutateAsync({ token: row.token, amount: row.claimableAmount });
8996
9527
  };
8997
9528
  return /* @__PURE__ */ jsx55(
@@ -9376,7 +9907,7 @@ var MeasuredContentRenderer = ({ children, id, isModal, onModalOverlayClick }) =
9376
9907
  ] });
9377
9908
  };
9378
9909
  var ContentRenderer = ({ page }) => {
9379
- const content = useMemo32(() => {
9910
+ const content = useMemo33(() => {
9380
9911
  const PageComp = pages[page.page];
9381
9912
  const props = page.props ?? {};
9382
9913
  return /* @__PURE__ */ jsx62(PageComp, { ...props });
@@ -9384,7 +9915,7 @@ var ContentRenderer = ({ page }) => {
9384
9915
  return /* @__PURE__ */ jsx62(MeasuredContentRenderer, { id: "content", children: content });
9385
9916
  };
9386
9917
  var ModalRenderer = ({ modal }) => {
9387
- const content = useMemo32(() => {
9918
+ const content = useMemo33(() => {
9388
9919
  const ModalComp = modals[modal.modal];
9389
9920
  return /* @__PURE__ */ jsx62(ModalComp, { ...modal });
9390
9921
  }, [modal]);
@@ -9597,21 +10128,21 @@ function createCofheConfig(config) {
9597
10128
  }
9598
10129
 
9599
10130
  // src/providers/CofheProvider.tsx
9600
- var import_ts_essentials23 = __toESM(require_dist(), 1);
10131
+ var import_ts_essentials25 = __toESM(require_dist(), 1);
9601
10132
  import { chains } from "@cofhe/sdk/chains";
9602
10133
  import { jsx as jsx66, jsxs as jsxs51 } from "react/jsx-runtime";
9603
10134
  var CofheContext = createContext4(void 0);
9604
10135
  function CofheProvider(props) {
9605
10136
  const { children, queryClient, publicClient, walletClient } = props;
9606
- const config = useMemo33(() => {
10137
+ const config = useMemo34(() => {
9607
10138
  if (props.config) return props.config;
9608
10139
  if (props.cofheClient) {
9609
- (0, import_ts_essentials23.assert)(config.environment === "react", "Provided cofheClient must have react config");
10140
+ (0, import_ts_essentials25.assert)(config.environment === "react", "Provided cofheClient must have react config");
9610
10141
  return props.cofheClient.config;
9611
10142
  }
9612
10143
  return createCofheConfig({ supportedChains: Object.values(chains) });
9613
10144
  }, [props.config, props.cofheClient]);
9614
- const cofheClient = useMemo33(() => props.cofheClient ?? createCofheClient(config), [props.cofheClient, config]);
10145
+ const cofheClient = useMemo34(() => props.cofheClient ?? createCofheClient(config), [props.cofheClient, config]);
9615
10146
  const [position, setPosition] = useState15(config.react.position);
9616
10147
  const [theme, setTheme] = useState15(config.react.initialTheme);
9617
10148
  return /* @__PURE__ */ jsx66(
@@ -9646,7 +10177,7 @@ function useCofheContext() {
9646
10177
  }
9647
10178
 
9648
10179
  // src/components/FnxEncryptInput.tsx
9649
- import { useState as useState16, useRef as useRef10, useEffect as useEffect14, useMemo as useMemo34 } from "react";
10180
+ import { useState as useState16, useRef as useRef10, useEffect as useEffect14, useMemo as useMemo35 } from "react";
9650
10181
  import SecurityIcon from "@mui/icons-material/Security";
9651
10182
  import KeyboardArrowDownIcon2 from "@mui/icons-material/KeyboardArrowDown";
9652
10183
  import ContentCopyIcon from "@mui/icons-material/ContentCopy";
@@ -9684,7 +10215,7 @@ var FnxEncryptInput = ({
9684
10215
  isEncrypting,
9685
10216
  stepsState: { lastStep: lastEncryptionStep }
9686
10217
  } = useCofheEncrypt();
9687
- const { progress: encryptionProgress, label: encryptionProgressLabel } = useMemo34(() => {
10218
+ const { progress: encryptionProgress, label: encryptionProgressLabel } = useMemo35(() => {
9688
10219
  return lastEncryptionStep ? getStepConfig(lastEncryptionStep) : {
9689
10220
  progress: void 0,
9690
10221
  label: void 0
@@ -9982,6 +10513,9 @@ export {
9982
10513
  getBlockExplorerTokenUrl,
9983
10514
  getBlockExplorerTxUrl,
9984
10515
  getBlockExplorerUrl,
10516
+ getCofheTokenClaimUnshieldedCallArgs,
10517
+ getCofheTokenShieldCallArgs,
10518
+ getCofheTokenUnshieldCallArgs,
9985
10519
  isEthPair,
9986
10520
  isWrappedEthToken,
9987
10521
  statusToString,
@@ -9999,6 +10533,7 @@ export {
9999
10533
  useCofheReadContractAndDecrypt,
10000
10534
  useCofheRemovePermit,
10001
10535
  useCofheSelectPermit,
10536
+ useCofheSimulateWriteContract,
10002
10537
  useCofheTokenClaimUnshielded,
10003
10538
  useCofheTokenClaimable,
10004
10539
  useCofheTokenDecryptedBalance,
@@ -10014,6 +10549,7 @@ export {
10014
10549
  useCoingeckoContractMarketChartRange,
10015
10550
  useCoingeckoUsdPrice,
10016
10551
  useInternalQueryClient,
10552
+ useTokenAllowance,
10017
10553
  useTokensWithPublicBalances,
10018
10554
  useTransactionReceiptsByHash,
10019
10555
  useTransactionStore