@orderly.network/ui-transfer 3.1.9 → 3.1.10

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
@@ -1528,6 +1528,24 @@ var QuantityInput = (props) => {
1528
1528
  hintMessage && message
1529
1529
  ] });
1530
1530
  };
1531
+
1532
+ // src/components/slippage/slippage.utils.ts
1533
+ var getSlippageRiskLevel = (value, min, recommendedValue) => {
1534
+ if (value === void 0 || !Number.isFinite(value)) {
1535
+ return void 0;
1536
+ }
1537
+ if (value < min) {
1538
+ return "minimum";
1539
+ }
1540
+ if (value < recommendedValue) {
1541
+ return "low";
1542
+ }
1543
+ if (value > recommendedValue) {
1544
+ return "high";
1545
+ }
1546
+ return void 0;
1547
+ };
1548
+ var shouldDisableSlippageSave = (value, riskLevel) => !value || riskLevel === "minimum";
1531
1549
  var options = [0.5, 1, 2];
1532
1550
  var Slippage = (props) => {
1533
1551
  const { min = 0.2, max = 10, dp = 2 } = props;
@@ -1536,10 +1554,12 @@ var Slippage = (props) => {
1536
1554
  const [open, setOpen] = react.useState(false);
1537
1555
  const { t } = i18n.useTranslation();
1538
1556
  react.useEffect(() => {
1539
- if (props.value && !options.includes(props.value)) {
1557
+ if (props.value !== void 0 && !options.includes(props.value)) {
1558
+ setValue(void 0);
1540
1559
  setCustomValue(props.value.toString());
1541
1560
  } else {
1542
1561
  setValue(props.value);
1562
+ setCustomValue("");
1543
1563
  }
1544
1564
  }, [props.value, open]);
1545
1565
  const showSlippage = () => {
@@ -1559,7 +1579,7 @@ var Slippage = (props) => {
1559
1579
  }
1560
1580
  const d = new utils.Decimal(val);
1561
1581
  setValue(void 0);
1562
- if (d.lt(min)) {
1582
+ if (d.lt(min) && !props.riskGuidance) {
1563
1583
  setCustomValue(min.toString());
1564
1584
  } else if (d.gt(max)) {
1565
1585
  setCustomValue(max.toString());
@@ -1578,17 +1598,27 @@ var Slippage = (props) => {
1578
1598
  }
1579
1599
  return Promise.resolve(true);
1580
1600
  };
1581
- const disabled = !getValue();
1601
+ const currentValue = getValue();
1602
+ const riskLevel = props.riskGuidance ? getSlippageRiskLevel(
1603
+ currentValue,
1604
+ min,
1605
+ props.riskGuidance.recommendedValue
1606
+ ) : void 0;
1582
1607
  const errorMessage = react.useMemo(() => {
1583
- return props.validate?.(getValue());
1584
- }, [value, customValue, props.validate]);
1608
+ if (riskLevel === "minimum") {
1609
+ return props.riskGuidance?.minimumError;
1610
+ }
1611
+ return currentValue === void 0 ? void 0 : props.validate?.(currentValue);
1612
+ }, [currentValue, props.riskGuidance, props.validate, riskLevel]);
1613
+ const warningMessage = riskLevel === "low" ? props.riskGuidance?.lowWarning : riskLevel === "high" ? props.riskGuidance?.highWarning : void 0;
1614
+ const disabled = shouldDisableSlippageSave(currentValue, riskLevel);
1585
1615
  const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oui-text-2xs", children: [
1586
1616
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gapX: 1, itemAlign: "center", children: [
1587
1617
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "sm", intensity: 54, children: t("transfer.slippage.slippageTolerance") }),
1588
1618
  /* @__PURE__ */ jsxRuntime.jsx(
1589
1619
  ui.Tips,
1590
1620
  {
1591
- content: t("transfer.slippage.slippageTolerance.description"),
1621
+ content: props.riskGuidance?.tooltip ?? t("transfer.slippage.slippageTolerance.description"),
1592
1622
  title: t("common.tips"),
1593
1623
  classNamss: {
1594
1624
  trigger: "oui-mt-[2px] oui-size-4"
@@ -1633,7 +1663,17 @@ var Slippage = (props) => {
1633
1663
  }
1634
1664
  )
1635
1665
  ] }),
1636
- errorMessage && /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { mt: 6, children: /* @__PURE__ */ jsxRuntime.jsx(
1666
+ (props.riskGuidance?.helperText || props.message) && /* @__PURE__ */ jsxRuntime.jsx(
1667
+ ui.Text,
1668
+ {
1669
+ as: "div",
1670
+ size: "2xs",
1671
+ intensity: 54,
1672
+ className: "oui-mt-3 oui-leading-4",
1673
+ children: props.riskGuidance?.helperText || props.message
1674
+ }
1675
+ ),
1676
+ errorMessage && /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { mt: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
1637
1677
  ui.Text,
1638
1678
  {
1639
1679
  size: "2xs",
@@ -1641,7 +1681,8 @@ var Slippage = (props) => {
1641
1681
  className: "oui-w-full oui-text-center",
1642
1682
  children: errorMessage
1643
1683
  }
1644
- ) })
1684
+ ) }),
1685
+ !errorMessage && warningMessage && /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { mt: 3, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "div", size: "2xs", color: "warning", className: "oui-leading-4", children: warningMessage }) })
1645
1686
  ] });
1646
1687
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1647
1688
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -7226,7 +7267,96 @@ var SwapCoin = (props) => {
7226
7267
  }
7227
7268
  );
7228
7269
  };
7270
+ var getTrackedConvertRequestForAccount = (request, accountId) => request?.accountId === accountId ? request : void 0;
7271
+ var isValidConvertAmount = (value) => {
7272
+ const amount = Number(value);
7273
+ return Number.isFinite(amount) && amount > 0;
7274
+ };
7275
+ var getConvertTargetAmount = (submittedAmount) => isValidConvertAmount(submittedAmount) ? String(submittedAmount) : "-";
7276
+ var getConvertReceivedAmount = (record) => {
7277
+ if (!record || record.status !== "completed" && record.status !== "succeeded") {
7278
+ return "-";
7279
+ }
7280
+ if (isValidConvertAmount(record.received_qty)) {
7281
+ return String(record.received_qty);
7282
+ }
7283
+ const detailAmount = record.details.reduce((total, detail) => {
7284
+ if (detail.result?.toLowerCase() !== "succeeded" || !isValidConvertAmount(detail.received_qty)) {
7285
+ return total;
7286
+ }
7287
+ return total.plus(detail.received_qty);
7288
+ }, new utils.Decimal(0));
7289
+ return detailAmount.gt(0) ? detailAmount.toString() : "-";
7290
+ };
7291
+ var createTrackedConvertRequest = (record) => {
7292
+ const sourceEntry = Object.entries(record.converted_asset ?? {})[0];
7293
+ const sourceDetail = record.details[0];
7294
+ return {
7295
+ convertId: record.convert_id,
7296
+ previousMaxConvertId: record.convert_id - 1,
7297
+ sourceToken: sourceEntry?.[0] || sourceDetail?.converted_asset || "-",
7298
+ sourceAmount: String(
7299
+ sourceEntry?.[1] ?? sourceDetail?.converted_qty ?? "-"
7300
+ ),
7301
+ targetToken: record.received_asset || "USDC",
7302
+ targetAmount: getConvertTargetAmount(void 0)
7303
+ };
7304
+ };
7305
+ var getEffectiveConvertStatus = (record) => {
7306
+ if (record.status === "pending") {
7307
+ return "pending";
7308
+ }
7309
+ const hasFailedTransaction = record.details.some(
7310
+ (detail) => detail.result?.toLowerCase() === "failed"
7311
+ );
7312
+ if (hasFailedTransaction) {
7313
+ return "failed";
7314
+ }
7315
+ const hasOnlySucceededTransactions = record.status === "completed" && record.details.length > 0 && record.details.every(
7316
+ (detail) => detail.result?.toLowerCase() === "succeeded"
7317
+ );
7318
+ return hasOnlySucceededTransactions ? "succeeded" : record.status;
7319
+ };
7320
+ var getConvertProgressStatus = (status, isRefreshing, isDelayed) => {
7321
+ if (status !== "pending") {
7322
+ return status;
7323
+ }
7324
+ if (isRefreshing) {
7325
+ return "checking";
7326
+ }
7327
+ return isDelayed ? "delayed" : "pending";
7328
+ };
7329
+ var shouldDisplayConvertProgress = (status, isRecoveredRequest) => Boolean(status && (!isRecoveredRequest || status === "pending"));
7330
+ var recordContainsToken = (record, token) => {
7331
+ return Object.keys(record.converted_asset ?? {}).some(
7332
+ (asset) => asset.toLowerCase() === token.toLowerCase()
7333
+ ) || record.details.some(
7334
+ (detail) => detail.converted_asset?.toLowerCase() === token.toLowerCase()
7335
+ );
7336
+ };
7337
+ var findTrackedConvertRecord = (rows, request) => {
7338
+ if (request.convertId !== void 0) {
7339
+ return rows.find((record) => record.convert_id === request.convertId);
7340
+ }
7341
+ return rows.find(
7342
+ (record) => record.convert_id > request.previousMaxConvertId && record.type === "manual" && recordContainsToken(record, request.sourceToken)
7343
+ );
7344
+ };
7345
+ var findLatestPendingConvertRecord = (rows) => rows.find((record) => getEffectiveConvertStatus(record) === "pending");
7346
+ var getConvertIdFromResponse = (response) => {
7347
+ if (!response || typeof response !== "object") {
7348
+ return void 0;
7349
+ }
7350
+ const value = response;
7351
+ const convertId = value.data?.convertId ?? value.data?.convert_id ?? value.convertId ?? value.convert_id;
7352
+ const numericConvertId = Number(convertId);
7353
+ return Number.isFinite(numericConvertId) ? numericConvertId : void 0;
7354
+ };
7355
+ var normalizeConvertSlippage = (value) => {
7356
+ return Number.isFinite(value) ? Math.max(0.2, value) : 0.5;
7357
+ };
7229
7358
  var ConvertFormUI = (props) => {
7359
+ const { t } = i18n.useTranslation();
7230
7360
  const {
7231
7361
  loading,
7232
7362
  disabled,
@@ -7247,8 +7377,26 @@ var ConvertFormUI = (props) => {
7247
7377
  currentLTV,
7248
7378
  nextLTV,
7249
7379
  networkId,
7250
- balanceRevalidating
7380
+ balanceRevalidating,
7381
+ activeConvertRequest,
7382
+ isCheckingConvertStatus,
7383
+ hasConvertStatusError,
7384
+ isConvertHistoryRefreshing,
7385
+ onRefreshConvertStatus,
7386
+ isRecoveredConvertRequest
7251
7387
  } = props;
7388
+ if (activeConvertRequest && shouldDisplayConvertProgress(
7389
+ activeConvertRequest.status,
7390
+ isRecoveredConvertRequest
7391
+ )) {
7392
+ return /* @__PURE__ */ jsxRuntime.jsx(
7393
+ ConvertProgressStatus,
7394
+ {
7395
+ request: activeConvertRequest,
7396
+ isRefreshing: isConvertHistoryRefreshing
7397
+ }
7398
+ );
7399
+ }
7252
7400
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { className: ui.textVariants({ weight: "semibold" }), children: [
7253
7401
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { className: "oui-mb-6 lg:oui-mb-8", children: [
7254
7402
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { mt: 3, mb: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -7292,7 +7440,21 @@ var ConvertFormUI = (props) => {
7292
7440
  targetSymbol: targetToken?.token
7293
7441
  }
7294
7442
  ),
7295
- /* @__PURE__ */ jsxRuntime.jsx(Slippage, { value: slippage, onValueChange: onSlippageChange }),
7443
+ /* @__PURE__ */ jsxRuntime.jsx(
7444
+ Slippage,
7445
+ {
7446
+ value: slippage,
7447
+ onValueChange: onSlippageChange,
7448
+ riskGuidance: {
7449
+ recommendedValue: 0.5,
7450
+ tooltip: t("transfer.convert.slippage.tooltip"),
7451
+ helperText: t("transfer.convert.slippage.helper"),
7452
+ minimumError: t("transfer.convert.slippage.minimumError"),
7453
+ lowWarning: t("transfer.convert.slippage.lowWarning"),
7454
+ highWarning: t("transfer.convert.slippage.highWarning")
7455
+ }
7456
+ }
7457
+ ),
7296
7458
  /* @__PURE__ */ jsxRuntime.jsx(
7297
7459
  MinimumReceived,
7298
7460
  {
@@ -7311,17 +7473,117 @@ var ConvertFormUI = (props) => {
7311
7473
  )
7312
7474
  ] })
7313
7475
  ] }),
7476
+ hasConvertStatusError && /* @__PURE__ */ jsxRuntime.jsxs(
7477
+ ui.Flex,
7478
+ {
7479
+ itemAlign: "center",
7480
+ justify: "between",
7481
+ gap: 3,
7482
+ className: "oui-mb-4 oui-w-full",
7483
+ children: [
7484
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { itemAlign: "center", gap: 2, children: [
7485
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ExclamationFillIcon, { className: "oui-size-4 oui-shrink-0 oui-text-warning-darken" }),
7486
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xs", intensity: 54, children: t("transfer.convert.statusCheckFailed") })
7487
+ ] }),
7488
+ /* @__PURE__ */ jsxRuntime.jsx(
7489
+ ui.Button,
7490
+ {
7491
+ size: "sm",
7492
+ variant: "outlined",
7493
+ loading: isConvertHistoryRefreshing,
7494
+ onClick: onRefreshConvertStatus,
7495
+ children: t("common.refresh")
7496
+ }
7497
+ )
7498
+ ]
7499
+ }
7500
+ ),
7314
7501
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { itemAlign: "center", justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
7315
7502
  ConvertAction,
7316
7503
  {
7317
7504
  networkId,
7318
7505
  disabled,
7319
- loading,
7506
+ loading: loading || isCheckingConvertStatus,
7320
7507
  onConvert
7321
7508
  }
7322
7509
  ) })
7323
7510
  ] });
7324
7511
  };
7512
+ var ConvertProgressStatus = ({ request, isRefreshing }) => {
7513
+ const { t } = i18n.useTranslation();
7514
+ const isPending = request.status === "pending";
7515
+ const isCompleted = request.status === "completed" || request.status === "succeeded";
7516
+ const isFailed = !isPending && !isCompleted;
7517
+ const progressStatus = getConvertProgressStatus(
7518
+ request.status,
7519
+ isRefreshing,
7520
+ request.isDelayed
7521
+ );
7522
+ const isChecking = progressStatus === "checking";
7523
+ const isDelayed = progressStatus === "delayed";
7524
+ const hasTargetAmount = isValidConvertAmount(request.targetAmount);
7525
+ const hasReceivedAmount = isValidConvertAmount(request.receivedAmount);
7526
+ const interpolation = {
7527
+ fromAmount: request.sourceAmount,
7528
+ fromToken: request.sourceToken,
7529
+ toAmount: request.targetAmount,
7530
+ toToken: request.targetToken,
7531
+ receivedAmount: request.receivedAmount
7532
+ };
7533
+ const statusLabel = isChecking ? t("transfer.convert.statusChecking") : isDelayed ? t("transfer.convert.delayed", interpolation) : isPending ? t("transfer.convert.pending") : isCompleted ? t("transfer.convert.completed") : t("transfer.convert.failed");
7534
+ const description = isDelayed ? t("transfer.convert.delayed.description") : isPending ? t(
7535
+ hasTargetAmount ? "transfer.convert.pending.description" : "transfer.convert.pending.descriptionWithoutAmount",
7536
+ interpolation
7537
+ ) : isCompleted ? t(
7538
+ hasReceivedAmount ? "transfer.convert.completed.description" : "transfer.convert.completed.descriptionWithoutAmount",
7539
+ interpolation
7540
+ ) : t("transfer.convert.failed.description");
7541
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7542
+ ui.Flex,
7543
+ {
7544
+ direction: "column",
7545
+ itemAlign: "center",
7546
+ gap: 6,
7547
+ className: "oui-w-full oui-px-4 oui-py-6",
7548
+ children: [
7549
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { itemAlign: "center", justify: "center", gap: 3, className: "oui-w-full", children: [
7550
+ isCompleted && /* @__PURE__ */ jsxRuntime.jsx(ui.CheckedCircleFillIcon, { className: "oui-size-4 oui-text-success" }),
7551
+ !isPending && !isCompleted && /* @__PURE__ */ jsxRuntime.jsx(ui.ExclamationFillIcon, { className: "oui-size-4 oui-text-danger" }),
7552
+ /* @__PURE__ */ jsxRuntime.jsx(
7553
+ ui.Text,
7554
+ {
7555
+ size: "sm",
7556
+ weight: "semibold",
7557
+ intensity: isFailed ? void 0 : 80,
7558
+ color: isFailed ? "danger" : void 0,
7559
+ className: "oui-text-center",
7560
+ children: statusLabel
7561
+ }
7562
+ ),
7563
+ isPending && !isDelayed && /* @__PURE__ */ jsxRuntime.jsx(
7564
+ ui.Flex,
7565
+ {
7566
+ itemAlign: "center",
7567
+ justify: "center",
7568
+ className: "oui-size-4 oui-shrink-0",
7569
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { size: "sm", className: "oui-block" })
7570
+ }
7571
+ )
7572
+ ] }),
7573
+ /* @__PURE__ */ jsxRuntime.jsx(
7574
+ ui.Text,
7575
+ {
7576
+ as: "div",
7577
+ size: "xs",
7578
+ intensity: 54,
7579
+ className: "oui-w-full oui-text-center",
7580
+ children: description
7581
+ }
7582
+ )
7583
+ ]
7584
+ }
7585
+ );
7586
+ };
7325
7587
  var splitTokenBySymbol = (items) => {
7326
7588
  return items.reduce(
7327
7589
  (result, item) => {
@@ -7374,17 +7636,41 @@ var useToken3 = (options2) => {
7374
7636
  targetToken
7375
7637
  };
7376
7638
  };
7639
+ var isValidAmount = (value) => {
7640
+ const amount = Number(value);
7641
+ return Number.isFinite(amount) && amount > 0;
7642
+ };
7643
+ var getQuoteTargetAmount = (estimatedValue, estimatedAmount, decimals) => {
7644
+ if (isValidAmount(estimatedValue)) {
7645
+ return estimatedValue;
7646
+ }
7647
+ if (isValidAmount(estimatedAmount) && Number.isInteger(decimals) && typeof decimals === "number" && decimals >= 0) {
7648
+ return new utils.Decimal(estimatedAmount).div(new utils.Decimal(10).pow(decimals)).toString();
7649
+ }
7650
+ return "-";
7651
+ };
7377
7652
  var calculateQuoteRate = (inputAmount, estimatedUsdcValue) => new utils.Decimal(estimatedUsdcValue).div(inputAmount).toString();
7378
7653
  var calculateMinimumReceived = (estimatedUsdcValue, slippageLimitPercent) => new utils.Decimal(estimatedUsdcValue).mul(new utils.Decimal(1).minus(new utils.Decimal(slippageLimitPercent).div(100))).toString();
7379
-
7380
- // src/components/convertForm/convertForm.script.tsx
7381
7654
  var ORDERLY_CONVERT_SLIPPAGE_KEY = "orderly_convert_slippage";
7655
+ var ORDERLY_TRACKED_CONVERT_KEY = "orderly_tracked_convert";
7382
7656
  var SWAP_QUOTE_DEBOUNCE_MS = 300;
7657
+ var CONVERT_HISTORY_REFRESH_INTERVAL = 1e4;
7658
+ var CONVERT_DELAY_WARNING_TIMEOUT = 2e4;
7659
+ var CONVERT_HISTORY_URL = "/v1/asset/convert_history?page=1&size=20";
7383
7660
  var getQuoteErrorMessage = (error) => error instanceof Error ? error.message : String(error);
7384
7661
  var useConvertFormScript = (options2) => {
7385
- const { token: defaultToken, close } = options2;
7662
+ const { token: defaultToken } = options2;
7386
7663
  const { t } = i18n.useTranslation();
7387
7664
  const [loading, setLoading] = react.useState(false);
7665
+ const [persistedTrackedConvert, setPersistedTrackedConvert] = hooks.useSessionStorage(
7666
+ ORDERLY_TRACKED_CONVERT_KEY,
7667
+ null
7668
+ );
7669
+ const [trackedConvert, setTrackedConvert] = react.useState(() => persistedTrackedConvert ?? void 0);
7670
+ const [isConvertDelayed, setIsConvertDelayed] = react.useState(false);
7671
+ const effectiveTrackedConvert = trackedConvert ?? persistedTrackedConvert;
7672
+ const trackedRequest = effectiveTrackedConvert?.request;
7673
+ const ee = hooks.useEventEmitter();
7388
7674
  const config = hooks.useConfig();
7389
7675
  const networkId = config.get("networkId");
7390
7676
  const [quantity, setQuantity] = react.useState("");
@@ -7405,19 +7691,145 @@ var useConvertFormScript = (options2) => {
7405
7691
  ORDERLY_CONVERT_SLIPPAGE_KEY,
7406
7692
  0.5
7407
7693
  );
7694
+ const normalizedSlippage = normalizeConvertSlippage(Number(slippage));
7695
+ react.useEffect(() => {
7696
+ if (normalizedSlippage !== slippage) {
7697
+ setSlippage(normalizedSlippage);
7698
+ }
7699
+ }, [normalizedSlippage, setSlippage, slippage]);
7700
+ const { state: accountState } = hooks.useAccount();
7701
+ const canQueryConvertHistory = accountState.status >= types.AccountStatusEnum.EnableTrading || accountState.status === types.AccountStatusEnum.EnableTradingWithoutConnected;
7702
+ const currentTrackedRequest = getTrackedConvertRequestForAccount(
7703
+ trackedRequest,
7704
+ accountState.accountId
7705
+ );
7706
+ react.useEffect(() => {
7707
+ if (!trackedConvert && persistedTrackedConvert) {
7708
+ setTrackedConvert(persistedTrackedConvert);
7709
+ }
7710
+ }, [persistedTrackedConvert, trackedConvert]);
7711
+ react.useEffect(() => {
7712
+ setIsConvertDelayed(false);
7713
+ }, [accountState.accountId]);
7714
+ const {
7715
+ data: convertHistory,
7716
+ error: convertHistoryError,
7717
+ isValidating: isConvertHistoryRefreshing,
7718
+ mutate: refreshConvertHistory
7719
+ } = hooks.usePrivateQuery(CONVERT_HISTORY_URL, {
7720
+ formatter: (data) => data,
7721
+ revalidateOnFocus: true,
7722
+ refreshInterval: (latestData) => {
7723
+ const rows = latestData?.rows ?? [];
7724
+ if (currentTrackedRequest) {
7725
+ const trackedRecord2 = findTrackedConvertRecord(
7726
+ rows,
7727
+ currentTrackedRequest
7728
+ );
7729
+ return !trackedRecord2 || getEffectiveConvertStatus(trackedRecord2) === "pending" ? CONVERT_HISTORY_REFRESH_INTERVAL : 0;
7730
+ }
7731
+ return findLatestPendingConvertRecord(rows) ? CONVERT_HISTORY_REFRESH_INTERVAL : 0;
7732
+ }
7733
+ });
7734
+ const convertHistoryRows = convertHistory?.rows ?? [];
7735
+ const latestPendingRecord = react.useMemo(
7736
+ () => findLatestPendingConvertRecord(convertHistoryRows),
7737
+ [convertHistoryRows]
7738
+ );
7739
+ const latestPendingRequest = react.useMemo(() => {
7740
+ if (!latestPendingRecord) {
7741
+ return void 0;
7742
+ }
7743
+ return {
7744
+ ...createTrackedConvertRequest(latestPendingRecord),
7745
+ accountId: accountState.accountId
7746
+ };
7747
+ }, [accountState.accountId, latestPendingRecord]);
7748
+ react.useEffect(() => {
7749
+ if (!currentTrackedRequest && latestPendingRequest) {
7750
+ setTrackedConvert({
7751
+ request: latestPendingRequest,
7752
+ origin: "recovered"
7753
+ });
7754
+ }
7755
+ }, [currentTrackedRequest, latestPendingRequest]);
7756
+ const effectiveTrackedRequest = currentTrackedRequest ?? latestPendingRequest;
7757
+ react.useEffect(() => {
7758
+ const handleConvertChange = () => {
7759
+ void refreshConvertHistory();
7760
+ };
7761
+ ee.on("assetconvert:changed", handleConvertChange);
7762
+ return () => {
7763
+ ee.off("assetconvert:changed", handleConvertChange);
7764
+ };
7765
+ }, [ee, refreshConvertHistory]);
7766
+ const trackedRecord = react.useMemo(
7767
+ () => effectiveTrackedRequest ? findTrackedConvertRecord(convertHistoryRows, effectiveTrackedRequest) : void 0,
7768
+ [convertHistoryRows, effectiveTrackedRequest]
7769
+ );
7770
+ const activeRecord = effectiveTrackedRequest ? trackedRecord : latestPendingRecord;
7771
+ const historyStatus = effectiveTrackedRequest ? trackedRecord ? getEffectiveConvertStatus(trackedRecord) : "pending" : activeRecord ? getEffectiveConvertStatus(activeRecord) : void 0;
7772
+ const activeStatus = historyStatus;
7773
+ react.useEffect(() => {
7774
+ if (persistedTrackedConvert && activeStatus && activeStatus !== "pending") {
7775
+ setPersistedTrackedConvert(null);
7776
+ }
7777
+ }, [activeStatus, persistedTrackedConvert, setPersistedTrackedConvert]);
7778
+ const trackedRequestKey = effectiveTrackedRequest ? `${effectiveTrackedRequest.convertId ?? "new"}:${effectiveTrackedRequest.previousMaxConvertId}:${effectiveTrackedRequest.sourceToken}:${effectiveTrackedRequest.targetToken}` : void 0;
7779
+ react.useEffect(() => {
7780
+ if (!trackedRequestKey || activeStatus !== "pending") {
7781
+ return;
7782
+ }
7783
+ const timer = window.setTimeout(() => {
7784
+ setIsConvertDelayed(true);
7785
+ }, CONVERT_DELAY_WARNING_TIMEOUT);
7786
+ return () => window.clearTimeout(timer);
7787
+ }, [activeStatus, trackedRequestKey]);
7408
7788
  const { maxAmount, convert } = hooks.useConvert({ token: sourceToken?.token });
7409
7789
  const onConvert = async () => {
7410
- if (loading) {
7790
+ if (loading || activeStatus === "pending" || canQueryConvertHistory && !convertHistory) {
7411
7791
  return;
7412
7792
  }
7413
7793
  setLoading(true);
7794
+ const requestSnapshot = {
7795
+ accountId: accountState.accountId,
7796
+ previousMaxConvertId: convertHistoryRows.reduce(
7797
+ (max, record) => Math.max(max, record.convert_id),
7798
+ 0
7799
+ ),
7800
+ sourceToken: sourceToken?.token || "-",
7801
+ sourceAmount: quantity,
7802
+ targetToken: targetToken?.token || "USDC",
7803
+ targetAmount: memoizedOutAmounts
7804
+ };
7414
7805
  return convert({
7415
7806
  amount: Number(quantity),
7416
- slippage: new utils.Decimal(slippage).div(100).toNumber()
7417
- }).then(() => {
7418
- ui.toast.success(t("transfer.convert.completed"));
7419
- close?.();
7420
- setQuantity("");
7807
+ slippage: new utils.Decimal(normalizedSlippage).div(100).toNumber()
7808
+ }).then((response) => {
7809
+ const nextTrackedConvert = {
7810
+ request: {
7811
+ ...requestSnapshot,
7812
+ convertId: getConvertIdFromResponse(response)
7813
+ },
7814
+ origin: "submitted"
7815
+ };
7816
+ setTrackedConvert(nextTrackedConvert);
7817
+ setPersistedTrackedConvert(nextTrackedConvert);
7818
+ ui.toast.success(
7819
+ /* @__PURE__ */ jsxRuntime.jsx(
7820
+ ui.ToastTile,
7821
+ {
7822
+ title: t("transfer.convert.request"),
7823
+ subtitle: t("transfer.convert.request.description", {
7824
+ fromAmount: requestSnapshot.sourceAmount,
7825
+ fromToken: requestSnapshot.sourceToken,
7826
+ toAmount: requestSnapshot.targetAmount,
7827
+ toToken: requestSnapshot.targetToken
7828
+ })
7829
+ }
7830
+ )
7831
+ );
7832
+ void refreshConvertHistory();
7421
7833
  }).catch((err) => {
7422
7834
  ui.toast.error(
7423
7835
  err.message?.includes("user rejected") ? t("transfer.convert.failed") : err.message
@@ -7426,6 +7838,9 @@ var useConvertFormScript = (options2) => {
7426
7838
  setLoading(false);
7427
7839
  });
7428
7840
  };
7841
+ const onRefreshConvertStatus = () => {
7842
+ void refreshConvertHistory();
7843
+ };
7429
7844
  const [
7430
7845
  postQuote,
7431
7846
  { data: quoteData, reset: resetQuote, isMutating: isQuoteLoading }
@@ -7438,9 +7853,9 @@ var useConvertFormScript = (options2) => {
7438
7853
  fromToken: sourceToken.token,
7439
7854
  toToken: targetToken.token,
7440
7855
  amount: new utils.Decimal(quantity).toNumber(),
7441
- slippage: new utils.Decimal(slippage).div(100).toNumber()
7856
+ slippage: new utils.Decimal(normalizedSlippage).div(100).toNumber()
7442
7857
  };
7443
- }, [quantity, slippage, sourceToken?.token, targetToken?.token]);
7858
+ }, [normalizedSlippage, quantity, sourceToken?.token, targetToken?.token]);
7444
7859
  react.useEffect(() => {
7445
7860
  resetQuote();
7446
7861
  if (!quoteRequest) {
@@ -7477,31 +7892,47 @@ var useConvertFormScript = (options2) => {
7477
7892
  }
7478
7893
  const fromToken = quoteData.fromToken;
7479
7894
  const toToken = quoteData.toToken;
7480
- if (!fromToken || !toToken || !fromToken.tokenAddress || !toToken.tokenAddress || !fromToken.amount || !toToken.estimatedAmount || !toToken.estimatedValue || !quoteData.gasEstimate || typeof quoteData.expiresAt !== "number") {
7895
+ if (!fromToken || !toToken || !fromToken.tokenAddress || !toToken.tokenAddress || !fromToken.amount || !quoteData.gasEstimate || typeof quoteData.expiresAt !== "number") {
7481
7896
  return false;
7482
7897
  }
7483
- return quoteData.expiresAt > Date.now();
7484
- }, [quoteData, quoteRequest]);
7898
+ const targetAmount2 = getQuoteTargetAmount(
7899
+ toToken.estimatedValue,
7900
+ toToken.estimatedAmount,
7901
+ targetToken?.decimals
7902
+ );
7903
+ return targetAmount2 !== "-" && quoteData.expiresAt > Date.now();
7904
+ }, [quoteData, quoteRequest, targetToken?.decimals]);
7485
7905
  react.useEffect(() => {
7486
7906
  if (quoteData && !isQuoteDataMatched) {
7487
7907
  resetQuote();
7488
7908
  }
7489
7909
  }, [isQuoteDataMatched, quoteData, resetQuote]);
7910
+ const quoteTargetAmount = react.useMemo(
7911
+ () => quoteData ? getQuoteTargetAmount(
7912
+ quoteData.toToken.estimatedValue,
7913
+ quoteData.toToken.estimatedAmount,
7914
+ targetToken?.decimals
7915
+ ) : "-",
7916
+ [quoteData, targetToken?.decimals]
7917
+ );
7490
7918
  const memoizedOutAmounts = react.useMemo(() => {
7491
7919
  if (quoteData && !isQuoteLoading && isQuoteDataMatched) {
7492
- return quoteData.toToken.estimatedValue;
7920
+ return quoteTargetAmount;
7493
7921
  }
7494
7922
  return "-";
7495
- }, [quoteData, isQuoteDataMatched, isQuoteLoading]);
7923
+ }, [isQuoteDataMatched, isQuoteLoading, quoteData, quoteTargetAmount]);
7496
7924
  const memoizedConvertRate = react.useMemo(() => {
7497
7925
  if (quoteData && quoteRequest && !isQuoteLoading && isQuoteDataMatched) {
7498
- return calculateQuoteRate(
7499
- quoteRequest.amount,
7500
- quoteData.toToken.estimatedValue
7501
- );
7926
+ return calculateQuoteRate(quoteRequest.amount, quoteTargetAmount);
7502
7927
  }
7503
7928
  return "-";
7504
- }, [isQuoteDataMatched, isQuoteLoading, quoteData, quoteRequest]);
7929
+ }, [
7930
+ isQuoteDataMatched,
7931
+ isQuoteLoading,
7932
+ quoteData,
7933
+ quoteRequest,
7934
+ quoteTargetAmount
7935
+ ]);
7505
7936
  const memoizedMinimumReceived = react.useMemo(() => {
7506
7937
  if (!quoteData || isQuoteLoading || !isQuoteDataMatched) {
7507
7938
  return "0";
@@ -7511,10 +7942,10 @@ var useConvertFormScript = (options2) => {
7511
7942
  return "0";
7512
7943
  }
7513
7944
  return calculateMinimumReceived(
7514
- quoteData.toToken.estimatedValue,
7945
+ quoteTargetAmount,
7515
7946
  effectiveSlippage.toString()
7516
7947
  );
7517
- }, [quoteData, isQuoteDataMatched, isQuoteLoading]);
7948
+ }, [quoteData, isQuoteDataMatched, isQuoteLoading, quoteTargetAmount]);
7518
7949
  react.useEffect(() => {
7519
7950
  if (!quoteData || !quoteRequest || !isQuoteDataMatched) {
7520
7951
  return;
@@ -7545,7 +7976,19 @@ var useConvertFormScript = (options2) => {
7545
7976
  input: Number(quantity),
7546
7977
  token: sourceToken?.token
7547
7978
  });
7548
- const disabled = !quantity || Number(quantity) === 0;
7979
+ const isCheckingConvertStatus = canQueryConvertHistory && !convertHistory && !convertHistoryError;
7980
+ const hasConvertStatusError = canQueryConvertHistory && !convertHistory && Boolean(convertHistoryError);
7981
+ const disabled = !quantity || Number(quantity) === 0 || isCheckingConvertStatus || hasConvertStatusError || activeStatus === "pending";
7982
+ const targetAmount = effectiveTrackedRequest ? getConvertTargetAmount(effectiveTrackedRequest.targetAmount) : "-";
7983
+ const activeConvertRequest = effectiveTrackedRequest ? {
7984
+ status: activeStatus,
7985
+ sourceToken: effectiveTrackedRequest.sourceToken,
7986
+ sourceAmount: effectiveTrackedRequest.sourceAmount,
7987
+ targetToken: effectiveTrackedRequest.targetToken,
7988
+ targetAmount,
7989
+ receivedAmount: trackedRecord ? getConvertReceivedAmount(trackedRecord) : targetAmount,
7990
+ isDelayed: isConvertDelayed
7991
+ } : void 0;
7549
7992
  const { hasPositions, onSettlePnl } = useSettlePnl();
7550
7993
  return {
7551
7994
  walletName,
@@ -7565,14 +8008,20 @@ var useConvertFormScript = (options2) => {
7565
8008
  hasPositions,
7566
8009
  onSettlePnl,
7567
8010
  networkId,
7568
- slippage,
7569
- onSlippageChange: setSlippage,
8011
+ slippage: normalizedSlippage,
8012
+ onSlippageChange: (value) => setSlippage(normalizeConvertSlippage(value)),
7570
8013
  convertRate: memoizedConvertRate,
7571
8014
  minimumReceived: memoizedMinimumReceived,
7572
8015
  outAmounts: memoizedOutAmounts,
7573
8016
  isQuoteLoading,
7574
8017
  currentLTV: currentLtv,
7575
- nextLTV
8018
+ nextLTV,
8019
+ activeConvertRequest,
8020
+ isRecoveredConvertRequest: Boolean(currentTrackedRequest) && effectiveTrackedConvert?.origin === "recovered" || !currentTrackedRequest && Boolean(latestPendingRequest),
8021
+ isCheckingConvertStatus,
8022
+ hasConvertStatusError,
8023
+ isConvertHistoryRefreshing,
8024
+ onRefreshConvertStatus
7576
8025
  };
7577
8026
  };
7578
8027
  var ConvertDialogId = "ConvertDialogId";
@@ -7581,12 +8030,16 @@ var ConvertFormWidget = (props) => {
7581
8030
  const state = useConvertFormScript(props);
7582
8031
  return /* @__PURE__ */ jsxRuntime.jsx(ConvertFormUI, { ...state });
7583
8032
  };
7584
- ui.registerSimpleDialog(ConvertDialogId, ConvertFormWidget, {
8033
+ var ConvertFormModalWidget = (props) => {
8034
+ const { visible } = ui.useModal();
8035
+ return visible ? /* @__PURE__ */ jsxRuntime.jsx(ConvertFormWidget, { ...props }) : null;
8036
+ };
8037
+ ui.registerSimpleDialog(ConvertDialogId, ConvertFormModalWidget, {
7585
8038
  size: "md",
7586
8039
  classNames: { content: "oui-border oui-border-line-6" },
7587
8040
  title: () => i18n.i18n.t("transfer.convert")
7588
8041
  });
7589
- ui.registerSimpleSheet(ConvertSheetId, ConvertFormWidget, {
8042
+ ui.registerSimpleSheet(ConvertSheetId, ConvertFormModalWidget, {
7590
8043
  title: () => i18n.i18n.t("transfer.convert")
7591
8044
  });
7592
8045
  var DepositStatus = (props) => {
@@ -8516,6 +8969,7 @@ exports.YieldBearingReminder = YieldBearingReminder;
8516
8969
  exports.checkIsAccountId = checkIsAccountId;
8517
8970
  exports.feeDecimalsOffset = feeDecimalsOffset;
8518
8971
  exports.getDepositKnownErrorMessage = getDepositKnownErrorMessage;
8972
+ exports.getEffectiveConvertStatus = getEffectiveConvertStatus;
8519
8973
  exports.getTransferErrorMessage = getTransferErrorMessage;
8520
8974
  exports.getUSDCToken = getUSDCToken;
8521
8975
  exports.getYieldBearingAsset = getYieldBearingAsset;