@unifold/ui-web 0.1.43 → 0.1.45

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.
Files changed (3) hide show
  1. package/dist/index.js +152 -112
  2. package/dist/index.mjs +152 -112
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -43501,6 +43501,28 @@ async function verifyRecipientAddress(request, publishableKey) {
43501
43501
  }
43502
43502
  return response.json();
43503
43503
  }
43504
+ async function checkHypercoreActivation(request, publishableKey) {
43505
+ const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43506
+ validatePublishableKey(pk);
43507
+ const response = await fetch(
43508
+ `${API_BASE_URL}/v1/public/addresses/hypercore/activation`,
43509
+ {
43510
+ method: "POST",
43511
+ headers: {
43512
+ accept: "application/json",
43513
+ "x-publishable-key": pk,
43514
+ "Content-Type": "application/json"
43515
+ },
43516
+ body: JSON.stringify(request)
43517
+ }
43518
+ );
43519
+ if (!response.ok) {
43520
+ throw new Error(
43521
+ `HyperCore activation check failed: ${response.statusText}`
43522
+ );
43523
+ }
43524
+ return response.json();
43525
+ }
43504
43526
  async function getExchanges(query, publishableKey) {
43505
43527
  const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43506
43528
  validatePublishableKey(pk);
@@ -61016,6 +61038,7 @@ async function sendHypercoreWithdraw(params) {
61016
61038
  async function detectBrowserWallet(chainType, senderAddress) {
61017
61039
  const win = typeof window !== "undefined" ? window : null;
61018
61040
  if (!win || !senderAddress) return null;
61041
+ if (getUserDisconnectedWallet()) return null;
61019
61042
  const anyWin = win;
61020
61043
  if (chainType === "solana") {
61021
61044
  const solProviders = [];
@@ -61049,28 +61072,44 @@ async function detectBrowserWallet(chainType, senderAddress) {
61049
61072
  evmProviders.push({ provider: p, name });
61050
61073
  }
61051
61074
  };
61052
- add(anyWin.phantom?.ethereum, "Phantom");
61053
- add(anyWin.coinbaseWalletExtension, "Coinbase");
61054
- add(anyWin.trustwallet?.ethereum, "Trust Wallet");
61055
- add(anyWin.okxwallet, "OKX Wallet");
61056
- if (anyWin.__eip6963Providers) {
61057
- for (const detail of anyWin.__eip6963Providers) {
61058
- const rdns = detail.info?.rdns || "";
61059
- let name = detail.info?.name || "Wallet";
61060
- if (rdns.includes("metamask")) name = "MetaMask";
61061
- else if (rdns.includes("rabby")) name = "Rabby";
61062
- else if (rdns.includes("rainbow")) name = "Rainbow";
61063
- add(detail.provider, name);
61064
- }
61065
- }
61066
- if (win.ethereum) {
61067
- const eth = win.ethereum;
61068
- let name = "Wallet";
61069
- if (eth.isMetaMask && !eth.isPhantom && !eth.isRabby) name = "MetaMask";
61070
- else if (eth.isRabby) name = "Rabby";
61071
- else if (eth.isRainbow) name = "Rainbow";
61072
- else if (eth.isCoinbaseWallet) name = "Coinbase";
61073
- add(eth, name);
61075
+ if (!anyWin.__eip6963Providers) {
61076
+ anyWin.__eip6963Providers = [];
61077
+ }
61078
+ const handleAnnouncement = (event) => {
61079
+ const { detail } = event;
61080
+ if (!detail?.info || !detail?.provider) return;
61081
+ const exists = anyWin.__eip6963Providers.some((p) => p.info.uuid === detail.info.uuid);
61082
+ if (!exists) anyWin.__eip6963Providers.push(detail);
61083
+ };
61084
+ win.addEventListener("eip6963:announceProvider", handleAnnouncement);
61085
+ win.dispatchEvent(new Event("eip6963:requestProvider"));
61086
+ win.removeEventListener("eip6963:announceProvider", handleAnnouncement);
61087
+ for (const detail of anyWin.__eip6963Providers) {
61088
+ const rdns = detail.info?.rdns || "";
61089
+ let name = detail.info?.name || "Wallet";
61090
+ if (rdns.includes("metamask")) name = "MetaMask";
61091
+ else if (rdns.includes("phantom")) name = "Phantom";
61092
+ else if (rdns.includes("coinbase")) name = "Coinbase";
61093
+ else if (rdns.includes("rabby")) name = "Rabby";
61094
+ else if (rdns.includes("rainbow")) name = "Rainbow";
61095
+ else if (rdns.includes("okx")) name = "OKX Wallet";
61096
+ else if (rdns.includes("trust")) name = "Trust Wallet";
61097
+ add(detail.provider, name);
61098
+ }
61099
+ if (evmProviders.length === 0) {
61100
+ add(anyWin.phantom?.ethereum, "Phantom");
61101
+ add(anyWin.coinbaseWalletExtension, "Coinbase");
61102
+ add(anyWin.trustwallet?.ethereum, "Trust Wallet");
61103
+ add(anyWin.okxwallet, "OKX Wallet");
61104
+ if (evmProviders.length === 0 && win.ethereum) {
61105
+ const eth = win.ethereum;
61106
+ let name = "Wallet";
61107
+ if (eth.isMetaMask && !eth.isPhantom && !eth.isRabby) name = "MetaMask";
61108
+ else if (eth.isRabby) name = "Rabby";
61109
+ else if (eth.isRainbow) name = "Rainbow";
61110
+ else if (eth.isCoinbaseWallet) name = "Coinbase";
61111
+ add(eth, name);
61112
+ }
61074
61113
  }
61075
61114
  for (const { provider, name } of evmProviders) {
61076
61115
  try {
@@ -61123,12 +61162,9 @@ function WithdrawForm({
61123
61162
  estimatedProcessingTime,
61124
61163
  maxSlippagePercent,
61125
61164
  priceImpactPercent,
61126
- detectedWallet,
61165
+ senderAddress,
61127
61166
  sourceChainId,
61128
61167
  sourceTokenAddress,
61129
- isWalletMatch,
61130
- connectedWalletName,
61131
- canWithdraw,
61132
61168
  onWithdraw,
61133
61169
  onWithdrawError,
61134
61170
  onDepositWalletCreation,
@@ -61263,12 +61299,43 @@ function WithdrawForm({
61263
61299
  destinationTokenAddress: selectedChain.token_address,
61264
61300
  recipientAddress: trimmedAddress
61265
61301
  });
61266
- const amountBaseUnit = computeBaseUnit(
61302
+ let amountBaseUnit = computeBaseUnit(
61267
61303
  balanceData.balanceBaseUnit,
61268
61304
  parseFloat(amount),
61269
61305
  inputUnit === "crypto" ? balanceCrypto : balanceUsdNum
61270
61306
  );
61271
- const humanAmount = toSafeDecimalString(cryptoAmountFromInput, sourceDecimals);
61307
+ let humanAmount = toSafeDecimalString(cryptoAmountFromInput, sourceDecimals);
61308
+ if (isHypercoreChain(sourceChainId)) {
61309
+ try {
61310
+ const check = await checkHypercoreActivation(
61311
+ {
61312
+ source_address: senderAddress,
61313
+ recipient_address: depositWallet.address
61314
+ },
61315
+ publishableKey
61316
+ );
61317
+ if (!check.user_exists) {
61318
+ const fee = check.activation_fee;
61319
+ const maxSendable = balanceCrypto - fee;
61320
+ if (maxSendable <= 0) {
61321
+ throw new Error(
61322
+ `Insufficient balance. A ${fee} USDC activation fee is required for the first transfer to this address.`
61323
+ );
61324
+ }
61325
+ const requestedAmount = parseFloat(humanAmount);
61326
+ if (requestedAmount > maxSendable) {
61327
+ humanAmount = toSafeDecimalString(maxSendable, sourceDecimals);
61328
+ amountBaseUnit = computeBaseUnit(
61329
+ balanceData.balanceBaseUnit,
61330
+ maxSendable,
61331
+ balanceCrypto
61332
+ );
61333
+ }
61334
+ }
61335
+ } catch (e) {
61336
+ if (e instanceof Error && e.message.includes("activation fee")) throw e;
61337
+ }
61338
+ }
61272
61339
  const txInfo = {
61273
61340
  sourceChainType,
61274
61341
  sourceChainId,
@@ -61283,43 +61350,67 @@ function WithdrawForm({
61283
61350
  withdrawIntentAddress: depositWallet.address,
61284
61351
  recipientAddress: trimmedAddress
61285
61352
  };
61286
- if (detectedWallet) {
61287
- if (detectedWallet.chainFamily === "evm" && isHypercoreChain(sourceChainId)) {
61288
- await sendHypercoreWithdraw({
61289
- provider: detectedWallet.provider,
61290
- fromAddress: detectedWallet.address,
61291
- depositWalletAddress: depositWallet.address,
61292
- sourceTokenAddress,
61353
+ const wallet = await detectBrowserWallet(sourceChainType, senderAddress);
61354
+ console.log("browser wallet", wallet);
61355
+ if (wallet) {
61356
+ try {
61357
+ if (wallet.chainFamily === "evm" && isHypercoreChain(sourceChainId)) {
61358
+ await sendHypercoreWithdraw({
61359
+ provider: wallet.provider,
61360
+ fromAddress: wallet.address,
61361
+ depositWalletAddress: depositWallet.address,
61362
+ sourceTokenAddress,
61363
+ amount: humanAmount,
61364
+ tokenSymbol,
61365
+ publishableKey
61366
+ });
61367
+ } else if (wallet.chainFamily === "evm") {
61368
+ await sendEvmWithdraw({
61369
+ provider: wallet.provider,
61370
+ fromAddress: wallet.address,
61371
+ depositWalletAddress: depositWallet.address,
61372
+ sourceTokenAddress,
61373
+ sourceChainId,
61374
+ amountBaseUnit
61375
+ });
61376
+ } else if (wallet.chainFamily === "solana") {
61377
+ await sendSolanaWithdraw({
61378
+ provider: wallet.provider,
61379
+ fromAddress: wallet.address,
61380
+ depositWalletAddress: depositWallet.address,
61381
+ sourceTokenAddress,
61382
+ amountBaseUnit,
61383
+ publishableKey
61384
+ });
61385
+ }
61386
+ } catch (walletErr) {
61387
+ console.error("[Unifold] Browser wallet send failed:", walletErr, {
61388
+ wallet: `${wallet.name} (${wallet.chainFamily})`,
61389
+ sourceChainId,
61293
61390
  amount: humanAmount,
61294
- tokenSymbol,
61295
- publishableKey
61391
+ amountBaseUnit,
61392
+ depositWallet: depositWallet.address
61296
61393
  });
61297
- } else if (detectedWallet.chainFamily === "evm") {
61298
- await sendEvmWithdraw({
61299
- provider: detectedWallet.provider,
61300
- fromAddress: detectedWallet.address,
61301
- depositWalletAddress: depositWallet.address,
61302
- sourceTokenAddress,
61394
+ throw walletErr;
61395
+ }
61396
+ } else if (onWithdraw) {
61397
+ try {
61398
+ await onWithdraw(txInfo);
61399
+ } catch (callbackErr) {
61400
+ console.error("[Unifold] onWithdraw callback failed:", callbackErr, {
61303
61401
  sourceChainId,
61304
- amountBaseUnit
61305
- });
61306
- } else if (detectedWallet.chainFamily === "solana") {
61307
- await sendSolanaWithdraw({
61308
- provider: detectedWallet.provider,
61309
- fromAddress: detectedWallet.address,
61310
- depositWalletAddress: depositWallet.address,
61311
- sourceTokenAddress,
61402
+ amount: humanAmount,
61312
61403
  amountBaseUnit,
61313
- publishableKey
61404
+ depositWallet: depositWallet.address
61314
61405
  });
61406
+ throw callbackErr;
61315
61407
  }
61316
- } else if (onWithdraw) {
61317
- await onWithdraw(txInfo);
61318
61408
  } else {
61319
61409
  throw new Error("No withdrawal method available. Please connect a wallet.");
61320
61410
  }
61321
61411
  onWithdrawSubmitted?.(txInfo);
61322
61412
  } catch (err) {
61413
+ console.error("[Unifold] Withdrawal failed:", err);
61323
61414
  const raw = err instanceof Error ? err.message : "Withdrawal failed. Please try again.";
61324
61415
  setSubmitError(raw.length > 120 ? "Withdrawal failed. Please try again." : raw);
61325
61416
  onWithdrawError?.({
@@ -61330,7 +61421,7 @@ function WithdrawForm({
61330
61421
  } finally {
61331
61422
  setIsSubmitting(false);
61332
61423
  }
61333
- }, [selectedToken, selectedChain, isFormValid, cryptoAmountFromInput, sourceDecimals, trimmedAddress, publishableKey, onWithdraw, detectedWallet, sourceTokenAddress, sourceChainId, onWithdrawError, onDepositWalletCreation, onWithdrawSubmitted, amount, inputUnit, balanceCrypto, balanceUsdNum, balanceData]);
61424
+ }, [selectedToken, selectedChain, isFormValid, cryptoAmountFromInput, sourceDecimals, trimmedAddress, publishableKey, onWithdraw, sourceChainType, senderAddress, sourceTokenAddress, sourceChainId, onWithdrawError, onDepositWalletCreation, onWithdrawSubmitted, amount, inputUnit, balanceCrypto, balanceUsdNum, balanceData]);
61334
61425
  return /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(import_jsx_runtime73.Fragment, { children: [
61335
61426
  /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("div", { children: [
61336
61427
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
@@ -61541,23 +61632,12 @@ function WithdrawForm({
61541
61632
  ] })
61542
61633
  ] })
61543
61634
  ] }),
61544
- !canWithdraw && !submitError && /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(
61545
- "div",
61546
- {
61547
- className: "uf-flex uf-items-start uf-gap-2.5 uf-p-3 uf-rounded-xl",
61548
- style: { backgroundColor: colors2.card, border: `1px solid ${colors2.border}` },
61549
- children: [
61550
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(Wallet, { className: "uf-w-4 uf-h-4 uf-flex-shrink-0 uf-mt-0.5", style: { color: colors2.warning } }),
61551
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("div", { className: "uf-text-xs", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No connected wallet detected. Please connect a wallet that matches your account to withdraw." })
61552
- ]
61553
- }
61554
- ),
61555
- isWalletMatch && connectedWalletName ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61635
+ /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61556
61636
  "button",
61557
61637
  {
61558
61638
  type: "button",
61559
61639
  onClick: handleWithdraw,
61560
- disabled: !isFormValid || !canWithdraw || isSubmitting || !selectedToken || !selectedChain,
61640
+ disabled: !isFormValid || isSubmitting || !selectedToken || !selectedChain,
61561
61641
  className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed uf-flex uf-items-center uf-justify-center uf-gap-2",
61562
61642
  style: {
61563
61643
  backgroundColor: colors2.primary,
@@ -61571,29 +61651,9 @@ function WithdrawForm({
61571
61651
  "Processing..."
61572
61652
  ] }) : isOverBalance ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Insufficient balance" }) : isBelowMinimum ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Minimum amount not met" }) : submitError ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Withdrawal failed. Try again" }) : /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(import_jsx_runtime73.Fragment, { children: [
61573
61653
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(Wallet, { className: "uf-w-4 uf-h-4" }),
61574
- "Withdraw from ",
61575
- connectedWalletName
61654
+ t8.withdraw
61576
61655
  ] })
61577
61656
  }
61578
- ) : /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61579
- "button",
61580
- {
61581
- type: "button",
61582
- onClick: handleWithdraw,
61583
- disabled: !isFormValid || !canWithdraw || isSubmitting || !selectedToken || !selectedChain,
61584
- className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed",
61585
- style: {
61586
- backgroundColor: colors2.primary,
61587
- color: colors2.primaryForeground,
61588
- fontFamily: fonts.medium,
61589
- borderRadius: components.button.borderRadius,
61590
- border: `${components.button.borderWidth}px solid ${components.button.borderColor}`
61591
- },
61592
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("span", { className: "uf-flex uf-items-center uf-justify-center uf-gap-2", children: [
61593
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(LoaderCircle, { className: "uf-w-4 uf-h-4 uf-animate-spin" }),
61594
- "Processing..."
61595
- ] }) : isOverBalance ? "Insufficient balance" : isBelowMinimum ? "Minimum amount not met" : submitError ? "Withdrawal failed. Try again" : t8.withdraw
61596
- }
61597
61657
  ),
61598
61658
  /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("div", { className: "uf-flex uf-items-center uf-justify-between uf-text-xs uf-pt-1", children: [
61599
61659
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("div", { children: footerLeft }),
@@ -61972,22 +62032,6 @@ function WithdrawModal({
61972
62032
  });
61973
62033
  const [selectedToken, setSelectedToken] = (0, import_react28.useState)(null);
61974
62034
  const [selectedChain, setSelectedChain] = (0, import_react28.useState)(null);
61975
- const [detectedWallet, setDetectedWallet] = (0, import_react28.useState)(null);
61976
- const connectedWalletName = detectedWallet?.name ?? null;
61977
- const isWalletMatch = !!detectedWallet;
61978
- (0, import_react28.useEffect)(() => {
61979
- if (!senderAddress || !open) {
61980
- setDetectedWallet(null);
61981
- return;
61982
- }
61983
- let cancelled = false;
61984
- detectBrowserWallet(sourceChainType, senderAddress).then((wallet) => {
61985
- if (!cancelled) setDetectedWallet(wallet);
61986
- });
61987
- return () => {
61988
- cancelled = true;
61989
- };
61990
- }, [senderAddress, sourceChainType, open]);
61991
62035
  const [view, setView] = (0, import_react28.useState)("form");
61992
62036
  const [withdrawDepositWalletId, setWithdrawDepositWalletId] = (0, import_react28.useState)();
61993
62037
  const [selectedExecution, setSelectedExecution] = (0, import_react28.useState)(null);
@@ -62080,7 +62124,6 @@ function WithdrawModal({
62080
62124
  if (chain) setSelectedChain(chain);
62081
62125
  }, [selectedToken]);
62082
62126
  const isSourceSupported = sourceValidation?.isSupported ?? null;
62083
- const canWithdraw = !!onWithdraw || isWalletMatch;
62084
62127
  const isAnyLoading = tokensLoading || isCheckingSourceToken;
62085
62128
  const withdrawPoweredByFooter = /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PoweredByUnifold, { color: colors2.foregroundMuted, className: "uf-flex uf-justify-center uf-shrink-0" }) });
62086
62129
  return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PortalContainerProvider, { value: hideOverlay ? containerEl : null, children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Dialog2, { open: hideOverlay || open, onOpenChange: hideOverlay ? void 0 : handleClose, modal: !hideOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
@@ -62111,7 +62154,7 @@ function WithdrawModal({
62111
62154
  /* ---------- Tracker view: execution list ---------- */
62112
62155
  /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(import_jsx_runtime76.Fragment, { children: [
62113
62156
  /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(DepositHeader, { title: "Withdrawal History", showBack: true, showClose: !hideOverlay, onBack: () => setView("form"), onClose: handleClose }),
62114
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-flex-col uf-gap-2", style: { minHeight: 200 }, children: allWithdrawals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "uf-text-sm", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No withdrawals to track yet" }) }) : allWithdrawals.map((ex) => /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
62157
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-h-[460px] uf-overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:uf-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-flex-col uf-gap-2", children: allWithdrawals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "uf-text-sm", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No withdrawals to track yet" }) }) : allWithdrawals.map((ex) => /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
62115
62158
  WithdrawExecutionItem,
62116
62159
  {
62117
62160
  execution: ex,
@@ -62121,7 +62164,7 @@ function WithdrawModal({
62121
62164
  }
62122
62165
  },
62123
62166
  ex.id
62124
- )) }),
62167
+ )) }) }),
62125
62168
  withdrawPoweredByFooter
62126
62169
  ] })
62127
62170
  ) : (
@@ -62161,12 +62204,9 @@ function WithdrawModal({
62161
62204
  estimatedProcessingTime: sourceValidation?.estimatedProcessingTime ?? null,
62162
62205
  maxSlippagePercent: sourceValidation?.maxSlippagePercent ?? null,
62163
62206
  priceImpactPercent: sourceValidation?.priceImpactPercent ?? null,
62164
- detectedWallet,
62207
+ senderAddress,
62165
62208
  sourceChainId,
62166
62209
  sourceTokenAddress,
62167
- isWalletMatch,
62168
- connectedWalletName,
62169
- canWithdraw,
62170
62210
  onWithdraw,
62171
62211
  onWithdrawError,
62172
62212
  onDepositWalletCreation: handleDepositWalletCreation,
package/dist/index.mjs CHANGED
@@ -43488,6 +43488,28 @@ async function verifyRecipientAddress(request, publishableKey) {
43488
43488
  }
43489
43489
  return response.json();
43490
43490
  }
43491
+ async function checkHypercoreActivation(request, publishableKey) {
43492
+ const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43493
+ validatePublishableKey(pk);
43494
+ const response = await fetch(
43495
+ `${API_BASE_URL}/v1/public/addresses/hypercore/activation`,
43496
+ {
43497
+ method: "POST",
43498
+ headers: {
43499
+ accept: "application/json",
43500
+ "x-publishable-key": pk,
43501
+ "Content-Type": "application/json"
43502
+ },
43503
+ body: JSON.stringify(request)
43504
+ }
43505
+ );
43506
+ if (!response.ok) {
43507
+ throw new Error(
43508
+ `HyperCore activation check failed: ${response.statusText}`
43509
+ );
43510
+ }
43511
+ return response.json();
43512
+ }
43491
43513
  async function getExchanges(query, publishableKey) {
43492
43514
  const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43493
43515
  validatePublishableKey(pk);
@@ -61003,6 +61025,7 @@ async function sendHypercoreWithdraw(params) {
61003
61025
  async function detectBrowserWallet(chainType, senderAddress) {
61004
61026
  const win = typeof window !== "undefined" ? window : null;
61005
61027
  if (!win || !senderAddress) return null;
61028
+ if (getUserDisconnectedWallet()) return null;
61006
61029
  const anyWin = win;
61007
61030
  if (chainType === "solana") {
61008
61031
  const solProviders = [];
@@ -61036,28 +61059,44 @@ async function detectBrowserWallet(chainType, senderAddress) {
61036
61059
  evmProviders.push({ provider: p, name });
61037
61060
  }
61038
61061
  };
61039
- add(anyWin.phantom?.ethereum, "Phantom");
61040
- add(anyWin.coinbaseWalletExtension, "Coinbase");
61041
- add(anyWin.trustwallet?.ethereum, "Trust Wallet");
61042
- add(anyWin.okxwallet, "OKX Wallet");
61043
- if (anyWin.__eip6963Providers) {
61044
- for (const detail of anyWin.__eip6963Providers) {
61045
- const rdns = detail.info?.rdns || "";
61046
- let name = detail.info?.name || "Wallet";
61047
- if (rdns.includes("metamask")) name = "MetaMask";
61048
- else if (rdns.includes("rabby")) name = "Rabby";
61049
- else if (rdns.includes("rainbow")) name = "Rainbow";
61050
- add(detail.provider, name);
61051
- }
61052
- }
61053
- if (win.ethereum) {
61054
- const eth = win.ethereum;
61055
- let name = "Wallet";
61056
- if (eth.isMetaMask && !eth.isPhantom && !eth.isRabby) name = "MetaMask";
61057
- else if (eth.isRabby) name = "Rabby";
61058
- else if (eth.isRainbow) name = "Rainbow";
61059
- else if (eth.isCoinbaseWallet) name = "Coinbase";
61060
- add(eth, name);
61062
+ if (!anyWin.__eip6963Providers) {
61063
+ anyWin.__eip6963Providers = [];
61064
+ }
61065
+ const handleAnnouncement = (event) => {
61066
+ const { detail } = event;
61067
+ if (!detail?.info || !detail?.provider) return;
61068
+ const exists = anyWin.__eip6963Providers.some((p) => p.info.uuid === detail.info.uuid);
61069
+ if (!exists) anyWin.__eip6963Providers.push(detail);
61070
+ };
61071
+ win.addEventListener("eip6963:announceProvider", handleAnnouncement);
61072
+ win.dispatchEvent(new Event("eip6963:requestProvider"));
61073
+ win.removeEventListener("eip6963:announceProvider", handleAnnouncement);
61074
+ for (const detail of anyWin.__eip6963Providers) {
61075
+ const rdns = detail.info?.rdns || "";
61076
+ let name = detail.info?.name || "Wallet";
61077
+ if (rdns.includes("metamask")) name = "MetaMask";
61078
+ else if (rdns.includes("phantom")) name = "Phantom";
61079
+ else if (rdns.includes("coinbase")) name = "Coinbase";
61080
+ else if (rdns.includes("rabby")) name = "Rabby";
61081
+ else if (rdns.includes("rainbow")) name = "Rainbow";
61082
+ else if (rdns.includes("okx")) name = "OKX Wallet";
61083
+ else if (rdns.includes("trust")) name = "Trust Wallet";
61084
+ add(detail.provider, name);
61085
+ }
61086
+ if (evmProviders.length === 0) {
61087
+ add(anyWin.phantom?.ethereum, "Phantom");
61088
+ add(anyWin.coinbaseWalletExtension, "Coinbase");
61089
+ add(anyWin.trustwallet?.ethereum, "Trust Wallet");
61090
+ add(anyWin.okxwallet, "OKX Wallet");
61091
+ if (evmProviders.length === 0 && win.ethereum) {
61092
+ const eth = win.ethereum;
61093
+ let name = "Wallet";
61094
+ if (eth.isMetaMask && !eth.isPhantom && !eth.isRabby) name = "MetaMask";
61095
+ else if (eth.isRabby) name = "Rabby";
61096
+ else if (eth.isRainbow) name = "Rainbow";
61097
+ else if (eth.isCoinbaseWallet) name = "Coinbase";
61098
+ add(eth, name);
61099
+ }
61061
61100
  }
61062
61101
  for (const { provider, name } of evmProviders) {
61063
61102
  try {
@@ -61110,12 +61149,9 @@ function WithdrawForm({
61110
61149
  estimatedProcessingTime,
61111
61150
  maxSlippagePercent,
61112
61151
  priceImpactPercent,
61113
- detectedWallet,
61152
+ senderAddress,
61114
61153
  sourceChainId,
61115
61154
  sourceTokenAddress,
61116
- isWalletMatch,
61117
- connectedWalletName,
61118
- canWithdraw,
61119
61155
  onWithdraw,
61120
61156
  onWithdrawError,
61121
61157
  onDepositWalletCreation,
@@ -61250,12 +61286,43 @@ function WithdrawForm({
61250
61286
  destinationTokenAddress: selectedChain.token_address,
61251
61287
  recipientAddress: trimmedAddress
61252
61288
  });
61253
- const amountBaseUnit = computeBaseUnit(
61289
+ let amountBaseUnit = computeBaseUnit(
61254
61290
  balanceData.balanceBaseUnit,
61255
61291
  parseFloat(amount),
61256
61292
  inputUnit === "crypto" ? balanceCrypto : balanceUsdNum
61257
61293
  );
61258
- const humanAmount = toSafeDecimalString(cryptoAmountFromInput, sourceDecimals);
61294
+ let humanAmount = toSafeDecimalString(cryptoAmountFromInput, sourceDecimals);
61295
+ if (isHypercoreChain(sourceChainId)) {
61296
+ try {
61297
+ const check = await checkHypercoreActivation(
61298
+ {
61299
+ source_address: senderAddress,
61300
+ recipient_address: depositWallet.address
61301
+ },
61302
+ publishableKey
61303
+ );
61304
+ if (!check.user_exists) {
61305
+ const fee = check.activation_fee;
61306
+ const maxSendable = balanceCrypto - fee;
61307
+ if (maxSendable <= 0) {
61308
+ throw new Error(
61309
+ `Insufficient balance. A ${fee} USDC activation fee is required for the first transfer to this address.`
61310
+ );
61311
+ }
61312
+ const requestedAmount = parseFloat(humanAmount);
61313
+ if (requestedAmount > maxSendable) {
61314
+ humanAmount = toSafeDecimalString(maxSendable, sourceDecimals);
61315
+ amountBaseUnit = computeBaseUnit(
61316
+ balanceData.balanceBaseUnit,
61317
+ maxSendable,
61318
+ balanceCrypto
61319
+ );
61320
+ }
61321
+ }
61322
+ } catch (e) {
61323
+ if (e instanceof Error && e.message.includes("activation fee")) throw e;
61324
+ }
61325
+ }
61259
61326
  const txInfo = {
61260
61327
  sourceChainType,
61261
61328
  sourceChainId,
@@ -61270,43 +61337,67 @@ function WithdrawForm({
61270
61337
  withdrawIntentAddress: depositWallet.address,
61271
61338
  recipientAddress: trimmedAddress
61272
61339
  };
61273
- if (detectedWallet) {
61274
- if (detectedWallet.chainFamily === "evm" && isHypercoreChain(sourceChainId)) {
61275
- await sendHypercoreWithdraw({
61276
- provider: detectedWallet.provider,
61277
- fromAddress: detectedWallet.address,
61278
- depositWalletAddress: depositWallet.address,
61279
- sourceTokenAddress,
61340
+ const wallet = await detectBrowserWallet(sourceChainType, senderAddress);
61341
+ console.log("browser wallet", wallet);
61342
+ if (wallet) {
61343
+ try {
61344
+ if (wallet.chainFamily === "evm" && isHypercoreChain(sourceChainId)) {
61345
+ await sendHypercoreWithdraw({
61346
+ provider: wallet.provider,
61347
+ fromAddress: wallet.address,
61348
+ depositWalletAddress: depositWallet.address,
61349
+ sourceTokenAddress,
61350
+ amount: humanAmount,
61351
+ tokenSymbol,
61352
+ publishableKey
61353
+ });
61354
+ } else if (wallet.chainFamily === "evm") {
61355
+ await sendEvmWithdraw({
61356
+ provider: wallet.provider,
61357
+ fromAddress: wallet.address,
61358
+ depositWalletAddress: depositWallet.address,
61359
+ sourceTokenAddress,
61360
+ sourceChainId,
61361
+ amountBaseUnit
61362
+ });
61363
+ } else if (wallet.chainFamily === "solana") {
61364
+ await sendSolanaWithdraw({
61365
+ provider: wallet.provider,
61366
+ fromAddress: wallet.address,
61367
+ depositWalletAddress: depositWallet.address,
61368
+ sourceTokenAddress,
61369
+ amountBaseUnit,
61370
+ publishableKey
61371
+ });
61372
+ }
61373
+ } catch (walletErr) {
61374
+ console.error("[Unifold] Browser wallet send failed:", walletErr, {
61375
+ wallet: `${wallet.name} (${wallet.chainFamily})`,
61376
+ sourceChainId,
61280
61377
  amount: humanAmount,
61281
- tokenSymbol,
61282
- publishableKey
61378
+ amountBaseUnit,
61379
+ depositWallet: depositWallet.address
61283
61380
  });
61284
- } else if (detectedWallet.chainFamily === "evm") {
61285
- await sendEvmWithdraw({
61286
- provider: detectedWallet.provider,
61287
- fromAddress: detectedWallet.address,
61288
- depositWalletAddress: depositWallet.address,
61289
- sourceTokenAddress,
61381
+ throw walletErr;
61382
+ }
61383
+ } else if (onWithdraw) {
61384
+ try {
61385
+ await onWithdraw(txInfo);
61386
+ } catch (callbackErr) {
61387
+ console.error("[Unifold] onWithdraw callback failed:", callbackErr, {
61290
61388
  sourceChainId,
61291
- amountBaseUnit
61292
- });
61293
- } else if (detectedWallet.chainFamily === "solana") {
61294
- await sendSolanaWithdraw({
61295
- provider: detectedWallet.provider,
61296
- fromAddress: detectedWallet.address,
61297
- depositWalletAddress: depositWallet.address,
61298
- sourceTokenAddress,
61389
+ amount: humanAmount,
61299
61390
  amountBaseUnit,
61300
- publishableKey
61391
+ depositWallet: depositWallet.address
61301
61392
  });
61393
+ throw callbackErr;
61302
61394
  }
61303
- } else if (onWithdraw) {
61304
- await onWithdraw(txInfo);
61305
61395
  } else {
61306
61396
  throw new Error("No withdrawal method available. Please connect a wallet.");
61307
61397
  }
61308
61398
  onWithdrawSubmitted?.(txInfo);
61309
61399
  } catch (err) {
61400
+ console.error("[Unifold] Withdrawal failed:", err);
61310
61401
  const raw = err instanceof Error ? err.message : "Withdrawal failed. Please try again.";
61311
61402
  setSubmitError(raw.length > 120 ? "Withdrawal failed. Please try again." : raw);
61312
61403
  onWithdrawError?.({
@@ -61317,7 +61408,7 @@ function WithdrawForm({
61317
61408
  } finally {
61318
61409
  setIsSubmitting(false);
61319
61410
  }
61320
- }, [selectedToken, selectedChain, isFormValid, cryptoAmountFromInput, sourceDecimals, trimmedAddress, publishableKey, onWithdraw, detectedWallet, sourceTokenAddress, sourceChainId, onWithdrawError, onDepositWalletCreation, onWithdrawSubmitted, amount, inputUnit, balanceCrypto, balanceUsdNum, balanceData]);
61411
+ }, [selectedToken, selectedChain, isFormValid, cryptoAmountFromInput, sourceDecimals, trimmedAddress, publishableKey, onWithdraw, sourceChainType, senderAddress, sourceTokenAddress, sourceChainId, onWithdrawError, onDepositWalletCreation, onWithdrawSubmitted, amount, inputUnit, balanceCrypto, balanceUsdNum, balanceData]);
61321
61412
  return /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(import_jsx_runtime73.Fragment, { children: [
61322
61413
  /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("div", { children: [
61323
61414
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
@@ -61528,23 +61619,12 @@ function WithdrawForm({
61528
61619
  ] })
61529
61620
  ] })
61530
61621
  ] }),
61531
- !canWithdraw && !submitError && /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(
61532
- "div",
61533
- {
61534
- className: "uf-flex uf-items-start uf-gap-2.5 uf-p-3 uf-rounded-xl",
61535
- style: { backgroundColor: colors2.card, border: `1px solid ${colors2.border}` },
61536
- children: [
61537
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(Wallet, { className: "uf-w-4 uf-h-4 uf-flex-shrink-0 uf-mt-0.5", style: { color: colors2.warning } }),
61538
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("div", { className: "uf-text-xs", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No connected wallet detected. Please connect a wallet that matches your account to withdraw." })
61539
- ]
61540
- }
61541
- ),
61542
- isWalletMatch && connectedWalletName ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61622
+ /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61543
61623
  "button",
61544
61624
  {
61545
61625
  type: "button",
61546
61626
  onClick: handleWithdraw,
61547
- disabled: !isFormValid || !canWithdraw || isSubmitting || !selectedToken || !selectedChain,
61627
+ disabled: !isFormValid || isSubmitting || !selectedToken || !selectedChain,
61548
61628
  className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed uf-flex uf-items-center uf-justify-center uf-gap-2",
61549
61629
  style: {
61550
61630
  backgroundColor: colors2.primary,
@@ -61558,29 +61638,9 @@ function WithdrawForm({
61558
61638
  "Processing..."
61559
61639
  ] }) : isOverBalance ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Insufficient balance" }) : isBelowMinimum ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Minimum amount not met" }) : submitError ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_jsx_runtime73.Fragment, { children: "Withdrawal failed. Try again" }) : /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)(import_jsx_runtime73.Fragment, { children: [
61560
61640
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(Wallet, { className: "uf-w-4 uf-h-4" }),
61561
- "Withdraw from ",
61562
- connectedWalletName
61641
+ t8.withdraw
61563
61642
  ] })
61564
61643
  }
61565
- ) : /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
61566
- "button",
61567
- {
61568
- type: "button",
61569
- onClick: handleWithdraw,
61570
- disabled: !isFormValid || !canWithdraw || isSubmitting || !selectedToken || !selectedChain,
61571
- className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed",
61572
- style: {
61573
- backgroundColor: colors2.primary,
61574
- color: colors2.primaryForeground,
61575
- fontFamily: fonts.medium,
61576
- borderRadius: components.button.borderRadius,
61577
- border: `${components.button.borderWidth}px solid ${components.button.borderColor}`
61578
- },
61579
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("span", { className: "uf-flex uf-items-center uf-justify-center uf-gap-2", children: [
61580
- /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(LoaderCircle, { className: "uf-w-4 uf-h-4 uf-animate-spin" }),
61581
- "Processing..."
61582
- ] }) : isOverBalance ? "Insufficient balance" : isBelowMinimum ? "Minimum amount not met" : submitError ? "Withdrawal failed. Try again" : t8.withdraw
61583
- }
61584
61644
  ),
61585
61645
  /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("div", { className: "uf-flex uf-items-center uf-justify-between uf-text-xs uf-pt-1", children: [
61586
61646
  /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("div", { children: footerLeft }),
@@ -61959,22 +62019,6 @@ function WithdrawModal({
61959
62019
  });
61960
62020
  const [selectedToken, setSelectedToken] = (0, import_react28.useState)(null);
61961
62021
  const [selectedChain, setSelectedChain] = (0, import_react28.useState)(null);
61962
- const [detectedWallet, setDetectedWallet] = (0, import_react28.useState)(null);
61963
- const connectedWalletName = detectedWallet?.name ?? null;
61964
- const isWalletMatch = !!detectedWallet;
61965
- (0, import_react28.useEffect)(() => {
61966
- if (!senderAddress || !open) {
61967
- setDetectedWallet(null);
61968
- return;
61969
- }
61970
- let cancelled = false;
61971
- detectBrowserWallet(sourceChainType, senderAddress).then((wallet) => {
61972
- if (!cancelled) setDetectedWallet(wallet);
61973
- });
61974
- return () => {
61975
- cancelled = true;
61976
- };
61977
- }, [senderAddress, sourceChainType, open]);
61978
62022
  const [view, setView] = (0, import_react28.useState)("form");
61979
62023
  const [withdrawDepositWalletId, setWithdrawDepositWalletId] = (0, import_react28.useState)();
61980
62024
  const [selectedExecution, setSelectedExecution] = (0, import_react28.useState)(null);
@@ -62067,7 +62111,6 @@ function WithdrawModal({
62067
62111
  if (chain) setSelectedChain(chain);
62068
62112
  }, [selectedToken]);
62069
62113
  const isSourceSupported = sourceValidation?.isSupported ?? null;
62070
- const canWithdraw = !!onWithdraw || isWalletMatch;
62071
62114
  const isAnyLoading = tokensLoading || isCheckingSourceToken;
62072
62115
  const withdrawPoweredByFooter = /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PoweredByUnifold, { color: colors2.foregroundMuted, className: "uf-flex uf-justify-center uf-shrink-0" }) });
62073
62116
  return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(PortalContainerProvider, { value: hideOverlay ? containerEl : null, children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Dialog2, { open: hideOverlay || open, onOpenChange: hideOverlay ? void 0 : handleClose, modal: !hideOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
@@ -62098,7 +62141,7 @@ function WithdrawModal({
62098
62141
  /* ---------- Tracker view: execution list ---------- */
62099
62142
  /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(import_jsx_runtime76.Fragment, { children: [
62100
62143
  /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(DepositHeader, { title: "Withdrawal History", showBack: true, showClose: !hideOverlay, onBack: () => setView("form"), onClose: handleClose }),
62101
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-flex-col uf-gap-2", style: { minHeight: 200 }, children: allWithdrawals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "uf-text-sm", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No withdrawals to track yet" }) }) : allWithdrawals.map((ex) => /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
62144
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-h-[460px] uf-overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:uf-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-flex-col uf-gap-2", children: allWithdrawals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "uf-text-sm", style: { color: colors2.foregroundMuted, fontFamily: fonts.regular }, children: "No withdrawals to track yet" }) }) : allWithdrawals.map((ex) => /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
62102
62145
  WithdrawExecutionItem,
62103
62146
  {
62104
62147
  execution: ex,
@@ -62108,7 +62151,7 @@ function WithdrawModal({
62108
62151
  }
62109
62152
  },
62110
62153
  ex.id
62111
- )) }),
62154
+ )) }) }),
62112
62155
  withdrawPoweredByFooter
62113
62156
  ] })
62114
62157
  ) : (
@@ -62148,12 +62191,9 @@ function WithdrawModal({
62148
62191
  estimatedProcessingTime: sourceValidation?.estimatedProcessingTime ?? null,
62149
62192
  maxSlippagePercent: sourceValidation?.maxSlippagePercent ?? null,
62150
62193
  priceImpactPercent: sourceValidation?.priceImpactPercent ?? null,
62151
- detectedWallet,
62194
+ senderAddress,
62152
62195
  sourceChainId,
62153
62196
  sourceTokenAddress,
62154
- isWalletMatch,
62155
- connectedWalletName,
62156
- canWithdraw,
62157
62197
  onWithdraw,
62158
62198
  onWithdrawError,
62159
62199
  onDepositWalletCreation: handleDepositWalletCreation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifold/ui-web",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "Unifold UI Web - Framework-agnostic deposit widget",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -27,10 +27,10 @@
27
27
  "@types/react-dom": "^19.0.0",
28
28
  "tsup": "^8.0.0",
29
29
  "typescript": "^5.0.0",
30
- "@unifold/connect-react": "0.1.43",
31
- "@unifold/ui-react": "0.1.43",
32
- "@unifold/core": "0.1.43",
33
- "@unifold/react-provider": "0.1.43"
30
+ "@unifold/connect-react": "0.1.45",
31
+ "@unifold/core": "0.1.45",
32
+ "@unifold/react-provider": "0.1.45",
33
+ "@unifold/ui-react": "0.1.45"
34
34
  },
35
35
  "keywords": [
36
36
  "unifold",