@unifold/ui-react 0.1.76 → 0.1.78

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
@@ -564,6 +564,9 @@ var en_default = {
564
564
  transactionBreakdown: "Transaction breakdown",
565
565
  networkFee: "Network fee",
566
566
  confirmOrder: "Confirm Order",
567
+ mfaEnrollmentTitle: "Enable SMS or Authy 2FA",
568
+ mfaEnrollmentMessage: "Please make sure you enable SMS or Authy 2FA in your Coinbase account. Press Continue after you enable it.",
569
+ mfaEnrollmentSettingsLink: "Open Coinbase security settings",
567
570
  termsNoticePrefix: "By clicking on Confirm Order, you agree to our ",
568
571
  termsNoticeLink: "terms",
569
572
  termsNoticeSuffix: "."
@@ -694,7 +697,12 @@ var en_default = {
694
697
  prefix: "By continuing, you agree to Coinbase's ",
695
698
  separator: ", ",
696
699
  lastSeparator: ", and ",
697
- suffix: ". Coinbase is the regulated provider of this fiat-to-crypto onramp."
700
+ suffix: ".",
701
+ agreements: {
702
+ guest_checkout_terms: "Terms of Service",
703
+ user_agreement: "User Agreement",
704
+ privacy_policy: "Privacy Policy"
705
+ }
698
706
  },
699
707
  errors: {
700
708
  rateLimited: "Too many attempts. Please wait a few minutes and try again.",
@@ -1355,6 +1363,9 @@ var zh_default = {
1355
1363
  transactionBreakdown: "\u4EA4\u6613\u660E\u7EC6",
1356
1364
  networkFee: "\u7F51\u7EDC\u624B\u7EED\u8D39",
1357
1365
  confirmOrder: "\u786E\u8BA4\u8BA2\u5355",
1366
+ mfaEnrollmentTitle: "\u8BF7\u542F\u7528\u77ED\u4FE1\u6216 Authy \u53CC\u91CD\u9A8C\u8BC1",
1367
+ mfaEnrollmentMessage: "\u8BF7\u786E\u8BA4\u60A8\u5DF2\u5728 Coinbase \u8D26\u6237\u4E2D\u542F\u7528\u77ED\u4FE1\u6216 Authy \u53CC\u91CD\u9A8C\u8BC1\u3002\u542F\u7528\u540E\u8BF7\u6309\u7EE7\u7EED\u3002",
1368
+ mfaEnrollmentSettingsLink: "\u6253\u5F00 Coinbase \u5B89\u5168\u8BBE\u7F6E",
1358
1369
  termsNoticePrefix: "\u70B9\u51FB\u201C\u786E\u8BA4\u8BA2\u5355\u201D\u5373\u8868\u793A\u60A8\u540C\u610F\u6211\u4EEC\u7684",
1359
1370
  termsNoticeLink: "\u6761\u6B3E",
1360
1371
  termsNoticeSuffix: "\u3002"
@@ -1485,7 +1496,12 @@ var zh_default = {
1485
1496
  prefix: "\u7EE7\u7EED\u5373\u8868\u793A\u60A8\u540C\u610F Coinbase \u7684",
1486
1497
  separator: "\u3001",
1487
1498
  lastSeparator: "\u548C",
1488
- suffix: "\u3002Coinbase \u662F\u672C\u6CD5\u5E01\u5151\u52A0\u5BC6\u8D27\u5E01\u5145\u503C\u670D\u52A1\u7684\u53D7\u76D1\u7BA1\u63D0\u4F9B\u65B9\u3002"
1499
+ suffix: "\u3002",
1500
+ agreements: {
1501
+ guest_checkout_terms: "\u670D\u52A1\u6761\u6B3E",
1502
+ user_agreement: "\u7528\u6237\u534F\u8BAE",
1503
+ privacy_policy: "\u9690\u79C1\u653F\u7B56"
1504
+ }
1489
1505
  },
1490
1506
  errors: {
1491
1507
  rateLimited: "\u5C1D\u8BD5\u6B21\u6570\u8FC7\u591A\u3002\u8BF7\u7A0D\u7B49\u51E0\u5206\u949F\u540E\u91CD\u8BD5\u3002",
@@ -2146,6 +2162,9 @@ var zh_TW_default = {
2146
2162
  transactionBreakdown: "\u4EA4\u6613\u660E\u7D30",
2147
2163
  networkFee: "\u7DB2\u8DEF\u624B\u7E8C\u8CBB",
2148
2164
  confirmOrder: "\u78BA\u8A8D\u8A02\u55AE",
2165
+ mfaEnrollmentTitle: "\u8ACB\u555F\u7528\u7C21\u8A0A\u6216 Authy \u96D9\u91CD\u9A57\u8B49",
2166
+ mfaEnrollmentMessage: "\u8ACB\u78BA\u8A8D\u60A8\u5DF2\u5728 Coinbase \u5E33\u6236\u4E2D\u555F\u7528\u7C21\u8A0A\u6216 Authy \u96D9\u91CD\u9A57\u8B49\u3002\u555F\u7528\u5F8C\u8ACB\u6309\u7E7C\u7E8C\u3002",
2167
+ mfaEnrollmentSettingsLink: "\u958B\u555F Coinbase \u5B89\u5168\u8A2D\u5B9A",
2149
2168
  termsNoticePrefix: "\u9EDE\u64CA\u300C\u78BA\u8A8D\u8A02\u55AE\u300D\u5373\u8868\u793A\u60A8\u540C\u610F\u6211\u5011\u7684",
2150
2169
  termsNoticeLink: "\u689D\u6B3E",
2151
2170
  termsNoticeSuffix: "\u3002"
@@ -2276,7 +2295,12 @@ var zh_TW_default = {
2276
2295
  prefix: "\u7E7C\u7E8C\u5373\u8868\u793A\u60A8\u540C\u610F Coinbase \u7684",
2277
2296
  separator: "\u3001",
2278
2297
  lastSeparator: "\u548C",
2279
- suffix: "\u3002Coinbase \u662F\u672C\u6CD5\u5E63\u514C\u52A0\u5BC6\u8CA8\u5E63\u5132\u503C\u670D\u52D9\u7684\u53D7\u76E3\u7BA1\u63D0\u4F9B\u65B9\u3002"
2298
+ suffix: "\u3002",
2299
+ agreements: {
2300
+ guest_checkout_terms: "\u670D\u52D9\u689D\u6B3E",
2301
+ user_agreement: "\u4F7F\u7528\u8005\u5354\u8B70",
2302
+ privacy_policy: "\u96B1\u79C1\u6B0A\u653F\u7B56"
2303
+ }
2280
2304
  },
2281
2305
  errors: {
2282
2306
  rateLimited: "\u5617\u8A66\u6B21\u6578\u904E\u591A\u3002\u8ACB\u7A0D\u7B49\u5E7E\u5206\u9418\u5F8C\u91CD\u8A66\u3002",
@@ -3203,10 +3227,12 @@ var import_lucide_react9 = require("lucide-react");
3203
3227
  var import_core9 = require("@unifold/core");
3204
3228
 
3205
3229
  // src/hooks/use-deposit-address.ts
3206
- var import_react = require("react");
3207
3230
  var import_react_query = require("@tanstack/react-query");
3208
3231
  var import_core = require("@unifold/core");
3209
3232
 
3233
+ // src/hooks/use-internal-user-id.ts
3234
+ var import_react = require("react");
3235
+
3210
3236
  // src/context/AnalyticsContext.tsx
3211
3237
  var React5 = __toESM(require("react"));
3212
3238
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -3238,6 +3264,15 @@ function useAnalytics() {
3238
3264
  return { track, startSession, endSession, getSessionId, setUserId, subscribe };
3239
3265
  }
3240
3266
 
3267
+ // src/hooks/use-internal-user-id.ts
3268
+ function useStampInternalUserId({ internalUserId }) {
3269
+ const { setUserId } = useAnalytics();
3270
+ (0, import_react.useEffect)(() => {
3271
+ if (!internalUserId) return;
3272
+ setUserId(internalUserId);
3273
+ }, [internalUserId, setUserId]);
3274
+ }
3275
+
3241
3276
  // src/hooks/use-deposit-address.ts
3242
3277
  function useDepositAddress(params) {
3243
3278
  const {
@@ -3251,7 +3286,6 @@ function useDepositAddress(params) {
3251
3286
  contractCalls,
3252
3287
  enabled = true
3253
3288
  } = params;
3254
- const { setUserId } = useAnalytics();
3255
3289
  const query = (0, import_react_query.useQuery)({
3256
3290
  queryKey: [
3257
3291
  "unifold",
@@ -3294,11 +3328,7 @@ function useDepositAddress(params) {
3294
3328
  retryDelay: (attempt) => Math.min(1e3 * 2 ** attempt, 1e4)
3295
3329
  // 1s, 2s, 4s (max 10s)
3296
3330
  });
3297
- (0, import_react.useEffect)(() => {
3298
- if (query.data?.user_id) {
3299
- setUserId(query.data.user_id);
3300
- }
3301
- }, [query.data?.user_id, setUserId]);
3331
+ useStampInternalUserId({ internalUserId: query.data?.user_id });
3302
3332
  return query;
3303
3333
  }
3304
3334
 
@@ -5568,6 +5598,10 @@ function BuyWithCard({
5568
5598
  wallet_address: wallet.address,
5569
5599
  subdivision_code: userIpInfo?.subdivisionCode || void 0,
5570
5600
  external_id: externalId,
5601
+ // The rail this quote was priced for, so the provider opens on it. Most
5602
+ // are `card`, which is also the server default, but a local rail (e.g.
5603
+ // `upi` in India) is the whole reason its provider was quoted.
5604
+ payment_method_type: selectedProvider.payment_method_type,
5571
5605
  // Exactly one of source_amount / destination_amount (mutually exclusive):
5572
5606
  // destination mode starts the session by the fixed crypto amount (fees on
5573
5607
  // top); otherwise by the entered fiat amount.
@@ -5585,7 +5619,11 @@ function BuyWithCard({
5585
5619
  method: "card",
5586
5620
  flow_type: "deposit",
5587
5621
  provider: selectedProvider.service_provider,
5588
- amount_usd: amount
5622
+ amount_usd: amount,
5623
+ // The execution only exists once the provider's payout lands on this
5624
+ // wallet, and it carries this id — so it's the join key for a purchase
5625
+ // that starts here and never reaches flow_completed.
5626
+ deposit_wallet_id: depositWalletId
5589
5627
  });
5590
5628
  onEvent?.({
5591
5629
  id: (0, import_core9.generatePrefixedKSUID)("sevt"),
@@ -7086,6 +7124,8 @@ var BuyWithWalletPay = React8.forwardRef(
7086
7124
  const [limitUpgradeError, setLimitUpgradeError] = (0, import_react15.useState)(null);
7087
7125
  const [upgradeSubmitting, setUpgradeSubmitting] = (0, import_react15.useState)(false);
7088
7126
  const upgradeFlowAbortRef = (0, import_react15.useRef)(null);
7127
+ const applyLimitRoutingRef = (0, import_react15.useRef)(() => {
7128
+ });
7089
7129
  const [amount, setAmount] = (0, import_react15.useState)(amountUsd ?? "");
7090
7130
  const [preparedOrder, setPreparedOrder] = (0, import_react15.useState)(null);
7091
7131
  const sessionAbortRef = (0, import_react15.useRef)(null);
@@ -7220,6 +7260,14 @@ var BuyWithWalletPay = React8.forwardRef(
7220
7260
  },
7221
7261
  [email, normalizedPhone, userId]
7222
7262
  );
7263
+ const { action: initialAction, refetchWalletPayLimits } = useWalletPayInitialScreen({
7264
+ userId,
7265
+ publishableKey,
7266
+ normalizedPhone,
7267
+ userEmail,
7268
+ sessionPhoneVerified: verificationSession?.phone?.status === "verified",
7269
+ isWaiting: defaultTokenLoading || legalAgreementsLoading || userIpInfoLoading
7270
+ });
7223
7271
  const createSessionAndSendOtp = (0, import_react15.useCallback)(async () => {
7224
7272
  setView("submitting_session");
7225
7273
  setErrorMessage("");
@@ -7229,7 +7277,12 @@ var BuyWithWalletPay = React8.forwardRef(
7229
7277
  email,
7230
7278
  phone: normalizedPhone,
7231
7279
  terms_accepted: true,
7232
- email_already_verified: true
7280
+ // TODO: when the API honors `false`, this will trigger email OTP.
7281
+ // Today `false` still skips email (we don't verify it) and JWT ev
7282
+ // is null. Don't send `true` — that was a fake attest.
7283
+ email_already_verified: false
7284
+ // Coinbase stays Redis-only + JWT until bank-transfer identity
7285
+ // is live. Do not persist from this path yet.
7233
7286
  },
7234
7287
  publishableKey
7235
7288
  );
@@ -7239,6 +7292,34 @@ var BuyWithWalletPay = React8.forwardRef(
7239
7292
  }
7240
7293
  setClientSecret(session.client_secret);
7241
7294
  storeWalletPaySession();
7295
+ if (session.status === "verified") {
7296
+ const tokenRes = await (0, import_core15.exchangeOnrampVerificationToken)(
7297
+ session.id,
7298
+ session.client_secret,
7299
+ publishableKey
7300
+ );
7301
+ setOnrampToken(tokenRes);
7302
+ storeWalletPaySession({
7303
+ emailVerifiedAt: session.email.verified_at,
7304
+ phoneVerifiedAt: session.phone.verified_at,
7305
+ onrampToken: tokenRes.token,
7306
+ onrampTokenExpiresAtMs: Date.now() + tokenRes.expires_in * 1e3
7307
+ });
7308
+ if (amountUsd) setAmount(amountUsd);
7309
+ try {
7310
+ const result = await refetchWalletPayLimits();
7311
+ if (result.data) {
7312
+ const nextView = getViewForLimitStatus(result.data);
7313
+ if (nextView) {
7314
+ applyLimitRoutingRef.current(nextView, result.data.upgrade_status);
7315
+ return;
7316
+ }
7317
+ }
7318
+ } catch {
7319
+ }
7320
+ setView("amount");
7321
+ return;
7322
+ }
7242
7323
  await (0, import_core15.sendOnrampVerificationOtp)(session.id, "phone", session.client_secret, publishableKey);
7243
7324
  setResendCooldown(30);
7244
7325
  setView("phone_otp");
@@ -7247,19 +7328,19 @@ var BuyWithWalletPay = React8.forwardRef(
7247
7328
  setErrorMessage(prettifyVerificationError(raw));
7248
7329
  setView("phone_input");
7249
7330
  }
7250
- }, [email, normalizedPhone, publishableKey, storeWalletPaySession]);
7331
+ }, [
7332
+ email,
7333
+ normalizedPhone,
7334
+ publishableKey,
7335
+ storeWalletPaySession,
7336
+ userId,
7337
+ amountUsd,
7338
+ refetchWalletPayLimits
7339
+ ]);
7251
7340
  const clearUpgradeFields = (0, import_react15.useCallback)(() => {
7252
7341
  setSsnLast4("");
7253
7342
  setDobInput("");
7254
7343
  }, []);
7255
- const { action: initialAction, refetchWalletPayLimits } = useWalletPayInitialScreen({
7256
- userId,
7257
- publishableKey,
7258
- normalizedPhone,
7259
- userEmail,
7260
- sessionPhoneVerified: verificationSession?.phone?.status === "verified",
7261
- isWaiting: defaultTokenLoading || legalAgreementsLoading || userIpInfoLoading
7262
- });
7263
7344
  const pollLimitUpgradeStatus = (0, import_react15.useCallback)(
7264
7345
  async (signal) => {
7265
7346
  const POLL_INTERVAL_MS5 = 1500;
@@ -7370,6 +7451,7 @@ var BuyWithWalletPay = React8.forwardRef(
7370
7451
  },
7371
7452
  [startUpgradePolling]
7372
7453
  );
7454
+ applyLimitRoutingRef.current = applyLimitRouting;
7373
7455
  const hasInitializedRef = (0, import_react15.useRef)(false);
7374
7456
  (0, import_react15.useEffect)(() => {
7375
7457
  if (hasInitializedRef.current) return;
@@ -7599,7 +7681,10 @@ var BuyWithWalletPay = React8.forwardRef(
7599
7681
  track("flow_started", {
7600
7682
  method,
7601
7683
  flow_type: "deposit",
7602
- amount_usd: walletPayAmountUsd
7684
+ amount_usd: walletPayAmountUsd,
7685
+ // No execution yet — one appears when the payout lands on this
7686
+ // wallet, carrying the same id.
7687
+ deposit_wallet_id: depositWalletId
7603
7688
  });
7604
7689
  onEvent?.({
7605
7690
  id: (0, import_core15.generatePrefixedKSUID)("sevt"),
@@ -7649,6 +7734,7 @@ var BuyWithWalletPay = React8.forwardRef(
7649
7734
  defaultTokenLoading,
7650
7735
  defaultTokenError,
7651
7736
  depositWallet?.address,
7737
+ depositWalletId,
7652
7738
  userEmail,
7653
7739
  publishableKey,
7654
7740
  onEvent,
@@ -8623,7 +8709,7 @@ var FALLBACK_AGREEMENTS = [
8623
8709
  {
8624
8710
  key: "guest_checkout_terms",
8625
8711
  name: "Guest Checkout Terms of Service",
8626
- url: "https://www.coinbase.com/legal/cb-pay/onramp-guest-checkout-terms"
8712
+ url: "https://www.coinbase.com/legal/guest-checkout/us"
8627
8713
  },
8628
8714
  {
8629
8715
  key: "user_agreement",
@@ -8643,22 +8729,24 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8643
8729
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
8644
8730
  "div",
8645
8731
  {
8646
- className: "uf-mt-3 uf-mb-3 uf-px-1 uf-space-y-1.5 uf-animate-pulse",
8732
+ className: "uf-mt-3 uf-mb-3 uf-px-1 uf-space-y-1.5 uf-animate-pulse uf-opacity-50",
8647
8733
  "aria-hidden": true,
8648
8734
  "aria-busy": "true",
8649
8735
  children: [
8650
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2.5 uf-rounded uf-w-full", style: { backgroundColor: colors2.card } }),
8651
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2.5 uf-rounded uf-w-11/12", style: { backgroundColor: colors2.card } }),
8652
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2.5 uf-rounded uf-w-3/4", style: { backgroundColor: colors2.card } })
8736
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2 uf-rounded uf-w-full", style: { backgroundColor: colors2.card } }),
8737
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2 uf-rounded uf-w-11/12", style: { backgroundColor: colors2.card } }),
8738
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2 uf-rounded uf-w-3/4", style: { backgroundColor: colors2.card } })
8653
8739
  ]
8654
8740
  }
8655
8741
  );
8656
8742
  }
8657
8743
  const agreements = legalAgreements?.agreements ?? FALLBACK_AGREEMENTS;
8744
+ const shortLabels = t.agreements;
8745
+ const lastSeparator = t.lastSeparator.replace(/\s+$/, "\xA0");
8658
8746
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
8659
8747
  "p",
8660
8748
  {
8661
- className: "uf-text-[11px] uf-leading-relaxed uf-mt-3 uf-mb-3 uf-px-1",
8749
+ className: "uf-text-[10px] uf-leading-relaxed uf-mt-3 uf-mb-3 uf-opacity-60 uf-text-center",
8662
8750
  style: { color: colors2.foregroundMuted, fontFamily: fonts.regular },
8663
8751
  children: [
8664
8752
  t.prefix,
@@ -8669,12 +8757,12 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8669
8757
  href: a.url,
8670
8758
  target: "_blank",
8671
8759
  rel: "noopener noreferrer",
8672
- className: "uf-underline",
8673
- style: { color: colors2.foreground },
8674
- children: a.name
8760
+ className: "uf-no-underline",
8761
+ style: { color: "inherit", textDecoration: "none", whiteSpace: "nowrap" },
8762
+ children: shortLabels[a.key] ?? a.name
8675
8763
  }
8676
8764
  ),
8677
- idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? t.lastSeparator : ""
8765
+ idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? lastSeparator : ""
8678
8766
  ] }, a.key)),
8679
8767
  t.suffix
8680
8768
  ]
@@ -9346,6 +9434,7 @@ function PayWithCashApp({
9346
9434
  destinationChainType,
9347
9435
  destinationChainId,
9348
9436
  destinationTokenAddress,
9437
+ contractCalls,
9349
9438
  cashAppIconUrl,
9350
9439
  view: controlledView,
9351
9440
  onViewChange,
@@ -9438,7 +9527,8 @@ function PayWithCashApp({
9438
9527
  destination_token_address: destinationTokenAddress,
9439
9528
  recipient_address: recipientAddress,
9440
9529
  source_amount: amount,
9441
- external_id: externalId
9530
+ external_id: externalId,
9531
+ ...contractCalls?.length ? { contract_calls: contractCalls } : {}
9442
9532
  },
9443
9533
  publishableKey
9444
9534
  );
@@ -9451,7 +9541,10 @@ function PayWithCashApp({
9451
9541
  track("flow_started", {
9452
9542
  method: "cashapp",
9453
9543
  flow_type: "deposit",
9454
- amount_usd: amount
9544
+ amount_usd: amount,
9545
+ // No execution yet — one appears when the payout lands on this
9546
+ // wallet, carrying the same id.
9547
+ deposit_wallet_id: depositWalletId
9455
9548
  });
9456
9549
  onEvent?.({
9457
9550
  id: (0, import_core19.generatePrefixedKSUID)("sevt"),
@@ -9476,6 +9569,8 @@ function PayWithCashApp({
9476
9569
  destinationChainType,
9477
9570
  destinationChainId,
9478
9571
  destinationTokenAddress,
9572
+ contractCalls,
9573
+ depositWalletId,
9479
9574
  onEvent,
9480
9575
  onDepositError,
9481
9576
  track,
@@ -10089,7 +10184,10 @@ function BankTransfer({
10089
10184
  method: "bank_transfer",
10090
10185
  flow_type: "deposit",
10091
10186
  provider: request.service_provider,
10092
- amount_usd: request.source_amount
10187
+ amount_usd: request.source_amount,
10188
+ // No execution yet — one appears when the provider's payout lands on
10189
+ // this wallet, carrying the same id.
10190
+ deposit_wallet_id: pollingWalletId
10093
10191
  });
10094
10192
  onEvent?.({
10095
10193
  id: (0, import_core21.generatePrefixedKSUID)("sevt"),
@@ -16329,7 +16427,11 @@ function PayWithStripeLink({
16329
16427
  if (checkoutResult.successful) {
16330
16428
  handledTerminalSessionRef.current = null;
16331
16429
  executionReconciledSessionRef.current = null;
16332
- track("flow_started", { method: "stripe_link", flow_type: "deposit" });
16430
+ track("flow_started", {
16431
+ method: "stripe_link",
16432
+ flow_type: "deposit",
16433
+ deposit_wallet_id: depositWalletId
16434
+ });
16333
16435
  setConfirmedSessionId(activeSessionId);
16334
16436
  setSessionStatus(STRIPE_SESSION_STATUS.FULFILLMENT_PROCESSING);
16335
16437
  if (selectedTokenSingleUseRef.current) clearSelectedPaymentToken();
@@ -17826,7 +17928,14 @@ function PayWithStripeLink({
17826
17928
  ]
17827
17929
  }
17828
17930
  ),
17829
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { ref: authMountRef, className: "uf-w-full uf-min-h-[200px]" })
17931
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
17932
+ "div",
17933
+ {
17934
+ ref: authMountRef,
17935
+ className: "uf-w-full uf-min-h-[200px]",
17936
+ style: { colorScheme: "only light" }
17937
+ }
17938
+ )
17830
17939
  ]
17831
17940
  }
17832
17941
  );
@@ -19107,6 +19216,9 @@ function CoinbaseConnect({
19107
19216
  flow_type: "deposit",
19108
19217
  method: "exchange_connect",
19109
19218
  provider: import_core27.IntegrationProvider.COINBASE,
19219
+ // No execution yet — one appears when the exchange's transfer lands on
19220
+ // this wallet, carrying the same id.
19221
+ deposit_wallet_id: transferDepositWalletId,
19110
19222
  amount_usd: sendAmount || void 0,
19111
19223
  currency: selectedAsset?.currency.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
19112
19224
  chain: sourceChain?.chain_id,
@@ -19114,6 +19226,34 @@ function CoinbaseConnect({
19114
19226
  network: sourceChain?.network
19115
19227
  });
19116
19228
  };
19229
+ const applyConfirmResult = (result) => {
19230
+ if (result.status === "mfa_required") {
19231
+ track("verification_started", {
19232
+ flow_type: "deposit",
19233
+ method: "exchange_connect",
19234
+ provider: import_core27.IntegrationProvider.COINBASE,
19235
+ verification_type: "mfa",
19236
+ verification_status: result.status
19237
+ });
19238
+ transitionTo("mfa");
19239
+ setMfaCode("");
19240
+ setMfaError(false);
19241
+ } else if (result.status === "mfa_enrollment_required") {
19242
+ transitionTo("mfa_enrollment");
19243
+ } else if (result.status === "completed") {
19244
+ trackTransferStarted();
19245
+ setConfirmResult(result);
19246
+ transitionTo("success");
19247
+ onTransferSuccess?.(result);
19248
+ } else {
19249
+ setErrorMessage(result.message || t.errors.transferFailed);
19250
+ transitionTo("error");
19251
+ onTransferError?.({
19252
+ message: result.message || t.errors.transferFailed,
19253
+ code: result.status
19254
+ });
19255
+ }
19256
+ };
19117
19257
  const handleConfirmTransfer = async () => {
19118
19258
  if (!transferIntent || !accessToken) return;
19119
19259
  setIsLoading(true);
@@ -19148,30 +19288,7 @@ function CoinbaseConnect({
19148
19288
  });
19149
19289
  throw err;
19150
19290
  });
19151
- if (result.status === "mfa_required") {
19152
- track("verification_started", {
19153
- flow_type: "deposit",
19154
- method: "exchange_connect",
19155
- provider: import_core27.IntegrationProvider.COINBASE,
19156
- verification_type: "mfa",
19157
- verification_status: result.status
19158
- });
19159
- transitionTo("mfa");
19160
- setMfaCode("");
19161
- setMfaError(false);
19162
- } else if (result.status === "completed") {
19163
- trackTransferStarted();
19164
- setConfirmResult(result);
19165
- transitionTo("success");
19166
- onTransferSuccess?.(result);
19167
- } else {
19168
- setErrorMessage(result.message || t.errors.transferFailed);
19169
- transitionTo("error");
19170
- onTransferError?.({
19171
- message: result.message || t.errors.transferFailed,
19172
- code: result.status
19173
- });
19174
- }
19291
+ applyConfirmResult(result);
19175
19292
  } catch (err) {
19176
19293
  const message = err instanceof Error ? err.message : t.errors.transferFailed;
19177
19294
  setErrorMessage(message);
@@ -19328,6 +19445,9 @@ function CoinbaseConnect({
19328
19445
  setShowTransferDetails(false);
19329
19446
  transitionTo("enter_amount");
19330
19447
  break;
19448
+ case "mfa_enrollment":
19449
+ transitionTo("transfer_confirm");
19450
+ break;
19331
19451
  case "mfa":
19332
19452
  transitionTo("transfer_confirm");
19333
19453
  break;
@@ -20884,6 +21004,55 @@ function CoinbaseConnect({
20884
21004
  ] })
20885
21005
  ] });
20886
21006
  }
21007
+ if (view === "mfa_enrollment") {
21008
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
21009
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(DepositHeader, { title: t.title, showBack: true, onBack: handleBack, onClose }),
21010
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
21011
+ "div",
21012
+ {
21013
+ className: "uf-flex uf-flex-col uf-items-center uf-px-4 uf-pb-4",
21014
+ style: viewTransitionStyle,
21015
+ children: [
21016
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
21017
+ DepositErrorState,
21018
+ {
21019
+ title: t.transfer.mfaEnrollmentTitle,
21020
+ message: t.transfer.mfaEnrollmentMessage
21021
+ }
21022
+ ),
21023
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
21024
+ "a",
21025
+ {
21026
+ href: "https://www.coinbase.com/settings/security",
21027
+ target: "_blank",
21028
+ rel: "noopener noreferrer",
21029
+ className: "uf-text-sm uf-underline uf-mb-8 uf-inline-flex uf-items-center uf-gap-1",
21030
+ style: { color: colors2.primary, fontFamily: fonts.medium },
21031
+ children: [
21032
+ t.transfer.mfaEnrollmentSettingsLink,
21033
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_lucide_react29.ExternalLink, { className: "uf-w-3.5 uf-h-3.5" })
21034
+ ]
21035
+ }
21036
+ ),
21037
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
21038
+ "button",
21039
+ {
21040
+ onClick: handleConfirmTransfer,
21041
+ disabled: isLoading,
21042
+ className: "uf-w-full uf-rounded-xl uf-py-3.5 uf-text-sm uf-font-medium uf-transition-all hover:uf-opacity-90 disabled:uf-opacity-50 uf-flex uf-items-center uf-justify-center uf-gap-2",
21043
+ style: {
21044
+ backgroundColor: colors2.primary,
21045
+ color: "#fff",
21046
+ fontFamily: fonts.medium
21047
+ },
21048
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_lucide_react29.Loader2, { className: "uf-w-4 uf-h-4 uf-animate-spin" }) : t.continue
21049
+ }
21050
+ )
21051
+ ]
21052
+ }
21053
+ )
21054
+ ] });
21055
+ }
20887
21056
  if (view === "mfa") {
20888
21057
  return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
20889
21058
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(DepositHeader, { title: t.title, showBack: true, onBack: handleBack, onClose }),
@@ -26990,7 +27159,11 @@ function WalletConnect({
26990
27159
  chain: String(token.chain_id),
26991
27160
  network: token.network,
26992
27161
  token: token.symbol ?? token.token_address,
26993
- currency: token.currency
27162
+ currency: token.currency,
27163
+ // No execution exists yet — it's created when this send lands on the
27164
+ // wallet. The wallet id is what that execution will carry, so it's
27165
+ // how a send that never completes is matched to what followed.
27166
+ deposit_wallet_id: activeDepositWallet?.id
26994
27167
  });
26995
27168
  setReceivedUsdAtSubmission(checkoutReceivedUsd ?? "0");
26996
27169
  setHasSignedTransaction(true);
@@ -27580,7 +27753,7 @@ function DepositModal({
27580
27753
  const applePaySubTitle = applePaySubTitleProp ?? t.applePayMenu.subtitle;
27581
27754
  const googlePayTitle = googlePayTitleProp ?? t.googlePayMenu.title;
27582
27755
  const googlePaySubTitle = googlePaySubTitleProp ?? t.googlePayMenu.subtitle;
27583
- const { track, startSession, endSession, getSessionId, subscribe } = useAnalytics();
27756
+ const { track, startSession, endSession, getSessionId, setUserId, subscribe } = useAnalytics();
27584
27757
  const normalizedPrefilledAmountUsd = (0, import_react31.useMemo)(
27585
27758
  () => normalizePrefilledUsdAmount(prefilledAmountUsd),
27586
27759
  [prefilledAmountUsd]
@@ -27588,6 +27761,7 @@ function DepositModal({
27588
27761
  const analyticsMethod = (method) => method === "transfer" ? "transfer_crypto" : method;
27589
27762
  const journeyActiveRef = (0, import_react31.useRef)(false);
27590
27763
  const journeySessionIdRef = (0, import_react31.useRef)(void 0);
27764
+ const journeyExternalUserIdRef = (0, import_react31.useRef)(void 0);
27591
27765
  const externalUserId = userId || void 0;
27592
27766
  const emitEvent = (0, import_react31.useCallback)(
27593
27767
  (event) => onEvent?.({ ...event, externalUserId }),
@@ -27615,6 +27789,9 @@ function DepositModal({
27615
27789
  method: analyticsMethod(method),
27616
27790
  flow_type: "deposit",
27617
27791
  amount_usd: execution?.destinationAmountUsd ?? execution?.sourceAmountUsd,
27792
+ // The one high-cardinality field we do send: the join key from a
27793
+ // funnel session back to the execution row it produced.
27794
+ execution_id: data.executionId ?? execution?.id,
27618
27795
  // Flat, low-cardinality execution dimensions for PostHog breakdowns
27619
27796
  // (precise/opaque fields like base units, addresses, tx hashes live in
27620
27797
  // the onEvent DirectExecution instead).
@@ -27642,6 +27819,7 @@ function DepositModal({
27642
27819
  method: analyticsMethod(method),
27643
27820
  flow_type: "deposit",
27644
27821
  error_code: error.code,
27822
+ execution_id: execution?.id,
27645
27823
  failure_reason: execution?.failureReason ?? void 0,
27646
27824
  status: execution?.status,
27647
27825
  source_chain_type: execution?.sourceChainType,
@@ -28070,8 +28248,10 @@ function DepositModal({
28070
28248
  const beginJourney = (0, import_react31.useCallback)(() => {
28071
28249
  journeySessionIdRef.current = startSession();
28072
28250
  journeyActiveRef.current = true;
28251
+ journeyExternalUserIdRef.current = userId;
28252
+ setUserId(null);
28073
28253
  track("widget_opened", { flow_type: "deposit" });
28074
- }, [startSession, track]);
28254
+ }, [startSession, track, setUserId, userId]);
28075
28255
  const handleClose = () => {
28076
28256
  finalizeJourney();
28077
28257
  onOpenChange(false);
@@ -28096,6 +28276,11 @@ function DepositModal({
28096
28276
  }
28097
28277
  beginJourney();
28098
28278
  }, [open]);
28279
+ (0, import_react31.useLayoutEffect)(() => {
28280
+ if (!open || journeyExternalUserIdRef.current === userId) return;
28281
+ journeyExternalUserIdRef.current = userId;
28282
+ setUserId(null);
28283
+ }, [open, userId, setUserId]);
28099
28284
  (0, import_react31.useLayoutEffect)(() => {
28100
28285
  if (!open) return;
28101
28286
  if (resetViewTimeoutRef.current) {
@@ -29007,6 +29192,7 @@ function DepositModal({
29007
29192
  destinationChainType,
29008
29193
  destinationChainId,
29009
29194
  destinationTokenAddress,
29195
+ contractCalls,
29010
29196
  cashAppIconUrl: projectConfig?.asset_cdn_url ? `${projectConfig.asset_cdn_url}/api/public/icons/onramps/svg/cashapp.svg` : void 0,
29011
29197
  view: cashAppView,
29012
29198
  onViewChange: setCashAppView,
@@ -29367,6 +29553,9 @@ function CheckoutModal({
29367
29553
  flowCompletedRef.current = true;
29368
29554
  track("flow_completed", {
29369
29555
  flow_type: "checkout",
29556
+ // Checkout has no direct execution; the intent is what a funnel
29557
+ // session joins back to.
29558
+ payment_intent_id: data.paymentIntentId,
29370
29559
  ...method ? { method: method === "transfer" ? "transfer_crypto" : method } : {}
29371
29560
  });
29372
29561
  }
@@ -29587,7 +29776,7 @@ function CheckoutModal({
29587
29776
  journeySessionIdRef.current = startSession();
29588
29777
  journeyActiveRef.current = true;
29589
29778
  flowCompletedRef.current = false;
29590
- setUserId(null);
29779
+ setUserId(paymentIntentUserId ?? null);
29591
29780
  track("widget_opened", { flow_type: "checkout" });
29592
29781
  if (resetViewTimeoutRef.current) {
29593
29782
  clearTimeout(resetViewTimeoutRef.current);
@@ -30028,7 +30217,7 @@ function CheckoutModal({
30028
30217
  }
30029
30218
 
30030
30219
  // src/components/withdrawals/WithdrawModal.tsx
30031
- var import_react39 = require("react");
30220
+ var import_react38 = require("react");
30032
30221
  var import_lucide_react44 = require("lucide-react");
30033
30222
 
30034
30223
  // src/hooks/use-supported-destination-tokens.ts
@@ -30194,7 +30383,7 @@ var import_react_query24 = require("@tanstack/react-query");
30194
30383
  var import_core50 = require("@unifold/core");
30195
30384
  function useExecutions(userId, publishableKey, options) {
30196
30385
  const actionType = options?.actionType ?? import_core50.ActionType.Deposit;
30197
- return (0, import_react_query24.useQuery)({
30386
+ const query = (0, import_react_query24.useQuery)({
30198
30387
  queryKey: ["unifold", "executions", actionType, userId, publishableKey],
30199
30388
  queryFn: () => (0, import_core50.queryExecutions)(userId, publishableKey, actionType),
30200
30389
  enabled: (options?.enabled ?? true) && !!userId,
@@ -30203,6 +30392,8 @@ function useExecutions(userId, publishableKey, options) {
30203
30392
  gcTime: 1e3 * 60 * 5,
30204
30393
  refetchOnWindowFocus: false
30205
30394
  });
30395
+ useStampInternalUserId({ internalUserId: query.data?.data[0]?.user_id });
30396
+ return query;
30206
30397
  }
30207
30398
 
30208
30399
  // src/hooks/use-withdraw-polling.ts
@@ -30537,7 +30728,7 @@ function WithdrawDoubleInput({
30537
30728
  }
30538
30729
 
30539
30730
  // src/components/withdrawals/WithdrawForm.tsx
30540
- var import_react37 = require("react");
30731
+ var import_react36 = require("react");
30541
30732
  var import_lucide_react42 = require("lucide-react");
30542
30733
  var import_core56 = require("@unifold/core");
30543
30734
 
@@ -30800,11 +30991,10 @@ async function detectBrowserWallet(chainType, senderAddress) {
30800
30991
  }
30801
30992
 
30802
30993
  // src/hooks/use-hypercore-withdraw-activation.ts
30803
- var import_react36 = require("react");
30994
+ var import_react35 = require("react");
30804
30995
  var import_core55 = require("@unifold/core");
30805
30996
 
30806
30997
  // src/hooks/use-get-deposit-address.ts
30807
- var import_react35 = require("react");
30808
30998
  var import_react_query26 = require("@tanstack/react-query");
30809
30999
  var import_core54 = require("@unifold/core");
30810
31000
  function useGetDepositAddress(params) {
@@ -30818,7 +31008,6 @@ function useGetDepositAddress(params) {
30818
31008
  actionType,
30819
31009
  enabled = true
30820
31010
  } = params;
30821
- const { setUserId } = useAnalytics();
30822
31011
  const canFire = !!userId && !!recipientAddress && !!destinationChainType && !!destinationChainId && !!destinationTokenAddress;
30823
31012
  const query = (0, import_react_query26.useQuery)({
30824
31013
  queryKey: [
@@ -30850,11 +31039,7 @@ function useGetDepositAddress(params) {
30850
31039
  refetchOnWindowFocus: false,
30851
31040
  retry: 1
30852
31041
  });
30853
- (0, import_react35.useEffect)(() => {
30854
- if (query.data?.user_id) {
30855
- setUserId(query.data.user_id);
30856
- }
30857
- }, [query.data?.user_id, setUserId]);
31042
+ useStampInternalUserId({ internalUserId: query.data?.user_id });
30858
31043
  return query;
30859
31044
  }
30860
31045
 
@@ -30895,7 +31080,7 @@ function useHypercoreWithdrawActivation(params) {
30895
31080
  actionType: import_core55.ActionType.Withdraw,
30896
31081
  enabled: enabled && isHypercore(sourceChainId)
30897
31082
  });
30898
- const depositWalletAddress = (0, import_react36.useMemo)(() => {
31083
+ const depositWalletAddress = (0, import_react35.useMemo)(() => {
30899
31084
  const wallets = depositWalletLookup.data?.data ?? [];
30900
31085
  return wallets.find((w) => w.chain_type === sourceChainType)?.address;
30901
31086
  }, [depositWalletLookup.data, sourceChainType]);
@@ -30986,22 +31171,22 @@ function WithdrawForm({
30986
31171
  const strings = useI18n().t;
30987
31172
  const t = strings.withdrawModal;
30988
31173
  const tCrypto = strings.transferCrypto;
30989
- const [recipientAddress, setRecipientAddress] = (0, import_react37.useState)(recipientAddressProp || "");
30990
- const [amount, setAmount] = (0, import_react37.useState)("");
30991
- const [inputUnit, setInputUnit] = (0, import_react37.useState)("fiat");
30992
- const [isSubmitting, setIsSubmitting] = (0, import_react37.useState)(false);
30993
- const [submitError, setSubmitError] = (0, import_react37.useState)(null);
30994
- const [detailsExpanded, setDetailsExpanded] = (0, import_react37.useState)(false);
30995
- const [glossaryOpen, setGlossaryOpen] = (0, import_react37.useState)(false);
30996
- const [isMaxed, setIsMaxed] = (0, import_react37.useState)(false);
30997
- (0, import_react37.useEffect)(() => {
31174
+ const [recipientAddress, setRecipientAddress] = (0, import_react36.useState)(recipientAddressProp || "");
31175
+ const [amount, setAmount] = (0, import_react36.useState)("");
31176
+ const [inputUnit, setInputUnit] = (0, import_react36.useState)("fiat");
31177
+ const [isSubmitting, setIsSubmitting] = (0, import_react36.useState)(false);
31178
+ const [submitError, setSubmitError] = (0, import_react36.useState)(null);
31179
+ const [detailsExpanded, setDetailsExpanded] = (0, import_react36.useState)(false);
31180
+ const [glossaryOpen, setGlossaryOpen] = (0, import_react36.useState)(false);
31181
+ const [isMaxed, setIsMaxed] = (0, import_react36.useState)(false);
31182
+ (0, import_react36.useEffect)(() => {
30998
31183
  setRecipientAddress(recipientAddressProp || "");
30999
31184
  setAmount("");
31000
31185
  setInputUnit("fiat");
31001
31186
  setSubmitError(null);
31002
31187
  setIsMaxed(false);
31003
31188
  }, [recipientAddressProp]);
31004
- (0, import_react37.useEffect)(() => {
31189
+ (0, import_react36.useEffect)(() => {
31005
31190
  setIsMaxed(false);
31006
31191
  }, [balanceData?.balanceBaseUnit]);
31007
31192
  const trimmedAddress = recipientAddress.trim();
@@ -31019,7 +31204,7 @@ function WithdrawForm({
31019
31204
  enabled: debouncedAddress.length > 5 && !!selectedChain
31020
31205
  });
31021
31206
  const isDebouncing = trimmedAddress !== debouncedAddress;
31022
- const addressError = (0, import_react37.useMemo)(() => {
31207
+ const addressError = (0, import_react36.useMemo)(() => {
31023
31208
  if (!trimmedAddress || trimmedAddress.length <= 5) return null;
31024
31209
  if (isDebouncing || isVerifyingAddress) return null;
31025
31210
  if (verifyError) return t.invalidAddress;
@@ -31063,33 +31248,33 @@ function WithdrawForm({
31063
31248
  destinationTokenAddress: selectedChain?.token_address,
31064
31249
  enabled: isAddressValid
31065
31250
  });
31066
- const exchangeRate = (0, import_react37.useMemo)(() => {
31251
+ const exchangeRate = (0, import_react36.useMemo)(() => {
31067
31252
  if (!balanceData?.exchangeRate) return 0;
31068
31253
  return parseFloat(balanceData.exchangeRate);
31069
31254
  }, [balanceData]);
31070
- const balanceCrypto = (0, import_react37.useMemo)(() => {
31255
+ const balanceCrypto = (0, import_react36.useMemo)(() => {
31071
31256
  if (!balanceData?.balanceHuman) return 0;
31072
31257
  return parseFloat(balanceData.balanceHuman);
31073
31258
  }, [balanceData]);
31074
- const balanceUsdNum = (0, import_react37.useMemo)(() => {
31259
+ const balanceUsdNum = (0, import_react36.useMemo)(() => {
31075
31260
  if (!balanceData?.balanceUsd) return 0;
31076
31261
  return parseFloat(balanceData.balanceUsd);
31077
31262
  }, [balanceData]);
31078
31263
  const tokenSymbol = sourceTokenSymbol || balanceData?.symbol || "TOKEN";
31079
31264
  const sourceDecimals = balanceData?.decimals ?? 6;
31080
- const cryptoAmountFromInput = (0, import_react37.useMemo)(() => {
31265
+ const cryptoAmountFromInput = (0, import_react36.useMemo)(() => {
31081
31266
  const val = parseFloat(amount);
31082
31267
  if (!val || val <= 0) return 0;
31083
31268
  if (inputUnit === "crypto") return val;
31084
31269
  return exchangeRate > 0 ? val / exchangeRate : 0;
31085
31270
  }, [amount, inputUnit, exchangeRate]);
31086
- const fiatAmountFromInput = (0, import_react37.useMemo)(() => {
31271
+ const fiatAmountFromInput = (0, import_react36.useMemo)(() => {
31087
31272
  const val = parseFloat(amount);
31088
31273
  if (!val || val <= 0) return 0;
31089
31274
  if (inputUnit === "fiat") return val;
31090
31275
  return val * exchangeRate;
31091
31276
  }, [amount, inputUnit, exchangeRate]);
31092
- const convertedDisplay = (0, import_react37.useMemo)(() => {
31277
+ const convertedDisplay = (0, import_react36.useMemo)(() => {
31093
31278
  if (!amount || parseFloat(amount) <= 0) return null;
31094
31279
  if (inputUnit === "crypto") {
31095
31280
  return `$${fiatAmountFromInput.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
@@ -31108,7 +31293,7 @@ function WithdrawForm({
31108
31293
  isMaxed,
31109
31294
  isStablecoin
31110
31295
  ]);
31111
- const balanceDisplay = (0, import_react37.useMemo)(() => {
31296
+ const balanceDisplay = (0, import_react36.useMemo)(() => {
31112
31297
  if (isLoadingBalance || !balanceData) return null;
31113
31298
  if (inputUnit === "crypto") {
31114
31299
  const displayDecimals = isStablecoin ? 2 : 6;
@@ -31131,7 +31316,7 @@ function WithdrawForm({
31131
31316
  tokenSymbol,
31132
31317
  isStablecoin
31133
31318
  ]);
31134
- const handleSwitchUnit = (0, import_react37.useCallback)(() => {
31319
+ const handleSwitchUnit = (0, import_react36.useCallback)(() => {
31135
31320
  if (isMaxed && balanceData) {
31136
31321
  if (inputUnit === "crypto") {
31137
31322
  setAmount((Math.round(balanceUsdNum * 100) / 100).toFixed(2));
@@ -31158,7 +31343,7 @@ function WithdrawForm({
31158
31343
  setInputUnit("crypto");
31159
31344
  }
31160
31345
  }, [amount, inputUnit, exchangeRate, sourceDecimals, isMaxed, balanceData, balanceUsdNum]);
31161
- const handleMaxClick = (0, import_react37.useCallback)(() => {
31346
+ const handleMaxClick = (0, import_react36.useCallback)(() => {
31162
31347
  if (inputUnit === "crypto") {
31163
31348
  if (balanceCrypto <= 0) return;
31164
31349
  setAmount(balanceData?.balanceHuman ?? "0");
@@ -31172,7 +31357,7 @@ function WithdrawForm({
31172
31357
  const isBelowMinimum = minimumWithdrawAmountUsd !== null && fiatAmountFromInput > 0 && Math.round(fiatAmountFromInput * 100) / 100 < minimumWithdrawAmountUsd;
31173
31358
  const isOverBalance = inputUnit === "crypto" ? cryptoAmountFromInput > 0 && balanceCrypto > 0 && cryptoAmountFromInput > balanceCrypto : fiatAmountFromInput > 0 && balanceUsdNum > 0 && Math.round(fiatAmountFromInput * 100) / 100 > Math.round(balanceUsdNum * 100) / 100;
31174
31359
  const isFormValid = trimmedAddress.length > 0 && amount.trim().length > 0 && cryptoAmountFromInput > 0 && isAddressValid && !isBelowMinimum && !isOverBalance && !isBalanceBelowMinimum && !!balanceData;
31175
- const handleWithdraw = (0, import_react37.useCallback)(async () => {
31360
+ const handleWithdraw = (0, import_react36.useCallback)(async () => {
31176
31361
  if (!selectedToken || !selectedChain) return;
31177
31362
  if (!isFormValid) return;
31178
31363
  setIsSubmitting(true);
@@ -31802,7 +31987,7 @@ function WithdrawExecutionItem({ execution, onClick }) {
31802
31987
  }
31803
31988
 
31804
31989
  // src/components/withdrawals/WithdrawConfirmingView.tsx
31805
- var import_react38 = require("react");
31990
+ var import_react37 = require("react");
31806
31991
  var import_jsx_runtime76 = require("react/jsx-runtime");
31807
31992
  function truncateAddress4(addr) {
31808
31993
  if (addr.length <= 12) return addr;
@@ -31818,9 +32003,9 @@ function WithdrawConfirmingView({
31818
32003
  const { colors: colors2, fonts, components } = useTheme();
31819
32004
  const { t: strings } = useI18n();
31820
32005
  const t = strings.withdrawModal.details;
31821
- const [showButton, setShowButton] = (0, import_react38.useState)(false);
32006
+ const [showButton, setShowButton] = (0, import_react37.useState)(false);
31822
32007
  const latestExecution = executions.length > 0 ? executions[executions.length - 1] : null;
31823
- (0, import_react38.useEffect)(() => {
32008
+ (0, import_react37.useEffect)(() => {
31824
32009
  if (latestExecution) return;
31825
32010
  const timer = setTimeout(() => setShowButton(true), SHOW_BUTTON_DELAY_MS);
31826
32011
  return () => clearTimeout(timer);
@@ -31970,13 +32155,13 @@ function WithdrawModal({
31970
32155
  const { colors: colors2, fonts, components } = useTheme();
31971
32156
  const t = useI18n().t.withdrawModal;
31972
32157
  const { track, startSession, endSession, getSessionId, setUserId, subscribe } = useAnalytics();
31973
- const emitEvent = (0, import_react39.useCallback)(
32158
+ const emitEvent = (0, import_react38.useCallback)(
31974
32159
  (event) => onEvent?.({ ...event, externalUserId }),
31975
32160
  [onEvent, externalUserId]
31976
32161
  );
31977
- const onEventRef = (0, import_react39.useRef)(emitEvent);
32162
+ const onEventRef = (0, import_react38.useRef)(emitEvent);
31978
32163
  onEventRef.current = emitEvent;
31979
- (0, import_react39.useEffect)(
32164
+ (0, import_react38.useEffect)(
31980
32165
  () => subscribe((event) => {
31981
32166
  if (event.properties?.flow_type !== "withdraw") return;
31982
32167
  const selection = selectionPayload(event);
@@ -31991,14 +32176,18 @@ function WithdrawModal({
31991
32176
  }),
31992
32177
  [subscribe, getSessionId]
31993
32178
  );
31994
- const journeyActiveRef = (0, import_react39.useRef)(false);
31995
- const journeySessionIdRef = (0, import_react39.useRef)(void 0);
31996
- const onWithdrawSuccessFor = (0, import_react39.useCallback)(
32179
+ const journeyActiveRef = (0, import_react38.useRef)(false);
32180
+ const journeySessionIdRef = (0, import_react38.useRef)(void 0);
32181
+ const journeyExternalUserIdRef = (0, import_react38.useRef)(void 0);
32182
+ const onWithdrawSuccessFor = (0, import_react38.useCallback)(
31997
32183
  (data) => {
31998
32184
  const execution = data.execution?.data.object;
31999
32185
  track("flow_completed", {
32000
32186
  flow_type: "withdraw",
32001
32187
  amount_usd: execution?.destinationAmountUsd ?? execution?.sourceAmountUsd,
32188
+ // The one high-cardinality field we do send: the join key from a
32189
+ // funnel session back to the execution row it produced.
32190
+ execution_id: data.executionId ?? execution?.id,
32002
32191
  // Flat, low-cardinality execution dimensions for PostHog breakdowns.
32003
32192
  status: execution?.status,
32004
32193
  source_chain_type: execution?.sourceChainType,
@@ -32013,12 +32202,13 @@ function WithdrawModal({
32013
32202
  },
32014
32203
  [onWithdrawSuccess, emitEvent, track]
32015
32204
  );
32016
- const onWithdrawErrorFor = (0, import_react39.useCallback)(
32205
+ const onWithdrawErrorFor = (0, import_react38.useCallback)(
32017
32206
  (error) => {
32018
32207
  const execution = error.execution?.data.object;
32019
32208
  track("flow_failed", {
32020
32209
  flow_type: "withdraw",
32021
32210
  error_code: error.code,
32211
+ execution_id: execution?.id,
32022
32212
  failure_reason: execution?.failureReason ?? void 0,
32023
32213
  status: execution?.status,
32024
32214
  source_chain_type: execution?.sourceChainType,
@@ -32033,14 +32223,14 @@ function WithdrawModal({
32033
32223
  },
32034
32224
  [onWithdrawError, emitEvent, track]
32035
32225
  );
32036
- const [containerEl, setContainerEl] = (0, import_react39.useState)(null);
32037
- const containerCallbackRef = (0, import_react39.useCallback)((el) => {
32226
+ const [containerEl, setContainerEl] = (0, import_react38.useState)(null);
32227
+ const containerCallbackRef = (0, import_react38.useCallback)((el) => {
32038
32228
  setContainerEl(el);
32039
32229
  }, []);
32040
- const [resolvedTheme, setResolvedTheme] = (0, import_react39.useState)(
32230
+ const [resolvedTheme, setResolvedTheme] = (0, import_react38.useState)(
32041
32231
  theme === "auto" ? "dark" : theme
32042
32232
  );
32043
- (0, import_react39.useEffect)(() => {
32233
+ (0, import_react38.useEffect)(() => {
32044
32234
  if (theme === "auto") {
32045
32235
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
32046
32236
  setResolvedTheme(mq.matches ? "dark" : "light");
@@ -32055,7 +32245,7 @@ function WithdrawModal({
32055
32245
  publishableKey,
32056
32246
  open
32057
32247
  );
32058
- const destinationTokens = (0, import_react39.useMemo)(() => tokensResponse?.data ?? [], [tokensResponse]);
32248
+ const destinationTokens = (0, import_react38.useMemo)(() => tokensResponse?.data ?? [], [tokensResponse]);
32059
32249
  const { data: sourceValidation, isLoading: isCheckingSourceToken } = useSourceTokenValidation({
32060
32250
  sourceChainType,
32061
32251
  sourceChainId,
@@ -32090,10 +32280,11 @@ function WithdrawModal({
32090
32280
  const selectedChain = selectedToken && selectedChainKey ? selectedToken.chains.find(
32091
32281
  (c) => getChainKey5(c.chain_id, c.chain_type) === selectedChainKey
32092
32282
  ) ?? null : null;
32093
- const [view, setView] = (0, import_react39.useState)("form");
32094
- const [withdrawDepositWalletId, setWithdrawDepositWalletId] = (0, import_react39.useState)();
32095
- const [selectedExecution, setSelectedExecution] = (0, import_react39.useState)(null);
32096
- const [submittedTxInfo, setSubmittedTxInfo] = (0, import_react39.useState)(null);
32283
+ const [view, setView] = (0, import_react38.useState)("form");
32284
+ const [withdrawDepositWalletId, setWithdrawDepositWalletId] = (0, import_react38.useState)();
32285
+ const withdrawDepositWalletIdRef = (0, import_react38.useRef)(void 0);
32286
+ const [selectedExecution, setSelectedExecution] = (0, import_react38.useState)(null);
32287
+ const [submittedTxInfo, setSubmittedTxInfo] = (0, import_react38.useState)(null);
32097
32288
  const { executions: realtimeExecutions } = useWithdrawPolling({
32098
32289
  userId: externalUserId,
32099
32290
  publishableKey,
@@ -32108,7 +32299,7 @@ function WithdrawModal({
32108
32299
  refetchInterval: view === "tracker" || view === "detail" ? 5e3 : 15e3
32109
32300
  });
32110
32301
  const allWithdrawals = allWithdrawalsData?.data ?? [];
32111
- const handleDepositWalletCreation = (0, import_react39.useCallback)(
32302
+ const handleDepositWalletCreation = (0, import_react38.useCallback)(
32112
32303
  async (params) => {
32113
32304
  const response = await (0, import_core58.createDepositAddress)(
32114
32305
  {
@@ -32129,12 +32320,13 @@ function WithdrawModal({
32129
32320
  if (!depositWallet) {
32130
32321
  throw new Error(`No deposit wallet available for ${sourceChainType}`);
32131
32322
  }
32323
+ withdrawDepositWalletIdRef.current = depositWallet.id;
32132
32324
  setWithdrawDepositWalletId(depositWallet.id);
32133
32325
  return depositWallet;
32134
32326
  },
32135
32327
  [externalUserId, publishableKey, sourceChainType, setUserId]
32136
32328
  );
32137
- const handleWithdrawStarted = (0, import_react39.useCallback)(
32329
+ const handleWithdrawStarted = (0, import_react38.useCallback)(
32138
32330
  (txInfo) => {
32139
32331
  const tokenData = destinationTokens.find((t2) => t2.symbol === txInfo.destinationTokenSymbol);
32140
32332
  const chainData = tokenData?.chains.find(
@@ -32148,7 +32340,12 @@ function WithdrawModal({
32148
32340
  currency: tokenData?.currency,
32149
32341
  amount: txInfo.amount,
32150
32342
  amount_base_unit: txInfo.amountBaseUnit,
32151
- ...txInfo.amountUsd ? { amount_usd: txInfo.amountUsd } : {}
32343
+ ...txInfo.amountUsd ? { amount_usd: txInfo.amountUsd } : {},
32344
+ // The execution doesn't exist yet — it's created when the user's send
32345
+ // lands on the withdraw deposit wallet, and carries this id. So a
32346
+ // withdrawal confirmed but never completed still joins to whatever
32347
+ // did (or didn't) follow on that wallet.
32348
+ deposit_wallet_id: withdrawDepositWalletIdRef.current
32152
32349
  });
32153
32350
  emitEvent({
32154
32351
  id: (0, import_core58.generatePrefixedKSUID)("sevt"),
@@ -32168,23 +32365,25 @@ function WithdrawModal({
32168
32365
  },
32169
32366
  [track, emitEvent, getSessionId, destinationTokens]
32170
32367
  );
32171
- const handleWithdrawSubmitted = (0, import_react39.useCallback)((txInfo) => {
32368
+ const handleWithdrawSubmitted = (0, import_react38.useCallback)((txInfo) => {
32172
32369
  setSubmittedTxInfo(txInfo);
32173
32370
  setView("confirming");
32174
32371
  }, []);
32175
- const resetViewTimeoutRef = (0, import_react39.useRef)(null);
32176
- const finalizeJourney = (0, import_react39.useCallback)(() => {
32372
+ const resetViewTimeoutRef = (0, import_react38.useRef)(null);
32373
+ const finalizeJourney = (0, import_react38.useCallback)(() => {
32177
32374
  if (!journeyActiveRef.current) return;
32178
32375
  journeyActiveRef.current = false;
32179
32376
  track("widget_closed", { flow_type: "withdraw" });
32180
32377
  if (getSessionId() === journeySessionIdRef.current) endSession();
32181
32378
  }, [track, getSessionId, endSession]);
32182
- const beginJourney = (0, import_react39.useCallback)(() => {
32379
+ const beginJourney = (0, import_react38.useCallback)(() => {
32183
32380
  journeySessionIdRef.current = startSession();
32184
32381
  journeyActiveRef.current = true;
32382
+ journeyExternalUserIdRef.current = externalUserId;
32383
+ setUserId(null);
32185
32384
  track("widget_opened", { flow_type: "withdraw" });
32186
- }, [startSession, track]);
32187
- const handleClose = (0, import_react39.useCallback)(() => {
32385
+ }, [startSession, track, setUserId, externalUserId]);
32386
+ const handleClose = (0, import_react38.useCallback)(() => {
32188
32387
  finalizeJourney();
32189
32388
  onOpenChange(false);
32190
32389
  if (resetViewTimeoutRef.current) clearTimeout(resetViewTimeoutRef.current);
@@ -32192,11 +32391,12 @@ function WithdrawModal({
32192
32391
  setView("form");
32193
32392
  setSelectedExecution(null);
32194
32393
  setSubmittedTxInfo(null);
32394
+ withdrawDepositWalletIdRef.current = void 0;
32195
32395
  setWithdrawDepositWalletId(void 0);
32196
32396
  resetViewTimeoutRef.current = null;
32197
32397
  }, 200);
32198
32398
  }, [onOpenChange, finalizeJourney]);
32199
- (0, import_react39.useLayoutEffect)(() => {
32399
+ (0, import_react38.useLayoutEffect)(() => {
32200
32400
  if (!open) {
32201
32401
  finalizeJourney();
32202
32402
  return;
@@ -32209,9 +32409,15 @@ function WithdrawModal({
32209
32409
  setView("form");
32210
32410
  setSelectedExecution(null);
32211
32411
  setSubmittedTxInfo(null);
32412
+ withdrawDepositWalletIdRef.current = void 0;
32212
32413
  setWithdrawDepositWalletId(void 0);
32213
32414
  }, [open]);
32214
- (0, import_react39.useEffect)(
32415
+ (0, import_react38.useLayoutEffect)(() => {
32416
+ if (!open || journeyExternalUserIdRef.current === externalUserId) return;
32417
+ journeyExternalUserIdRef.current = externalUserId;
32418
+ setUserId(null);
32419
+ }, [open, externalUserId, setUserId]);
32420
+ (0, import_react38.useEffect)(
32215
32421
  () => () => {
32216
32422
  if (journeyActiveRef.current) {
32217
32423
  journeyActiveRef.current = false;
@@ -32220,18 +32426,18 @@ function WithdrawModal({
32220
32426
  },
32221
32427
  [endSession, getSessionId]
32222
32428
  );
32223
- (0, import_react39.useEffect)(() => {
32429
+ (0, import_react38.useEffect)(() => {
32224
32430
  if (!open) return;
32225
32431
  const screen = view === "detail" ? "tracker_detail" : view;
32226
32432
  track("screen_viewed", { flow_type: "withdraw", screen });
32227
32433
  }, [open, view, track]);
32228
- (0, import_react39.useEffect)(
32434
+ (0, import_react38.useEffect)(
32229
32435
  () => () => {
32230
32436
  if (resetViewTimeoutRef.current) clearTimeout(resetViewTimeoutRef.current);
32231
32437
  },
32232
32438
  []
32233
32439
  );
32234
- const trackAssetSelected = (0, import_react39.useCallback)(
32440
+ const trackAssetSelected = (0, import_react38.useCallback)(
32235
32441
  (symbol, chainKey) => {
32236
32442
  const [chainType, chainId] = (chainKey ?? "").split(":");
32237
32443
  const tokenData = destinationTokens.find((t2) => t2.symbol === symbol);
@@ -32249,14 +32455,14 @@ function WithdrawModal({
32249
32455
  },
32250
32456
  [destinationTokens, track]
32251
32457
  );
32252
- const handleTokenSymbolChange = (0, import_react39.useCallback)(
32458
+ const handleTokenSymbolChange = (0, import_react38.useCallback)(
32253
32459
  (symbol) => {
32254
32460
  trackAssetSelected(symbol, selectedChainKey);
32255
32461
  setSelectedTokenSymbol(symbol);
32256
32462
  },
32257
32463
  [setSelectedTokenSymbol, selectedChainKey, trackAssetSelected]
32258
32464
  );
32259
- const handleChainKeyChange = (0, import_react39.useCallback)(
32465
+ const handleChainKeyChange = (0, import_react38.useCallback)(
32260
32466
  (chainKey) => {
32261
32467
  trackAssetSelected(selectedTokenSymbol, chainKey);
32262
32468
  setSelectedChainKey(chainKey);
@@ -32445,16 +32651,16 @@ function WithdrawModal({
32445
32651
  }
32446
32652
 
32447
32653
  // src/components/withdrawals/WithdrawTokenSelector.tsx
32448
- var import_react40 = require("react");
32654
+ var import_react39 = require("react");
32449
32655
  var import_lucide_react45 = require("lucide-react");
32450
32656
  var import_fuse2 = __toESM(require("fuse.js"));
32451
32657
  var import_jsx_runtime78 = require("react/jsx-runtime");
32452
32658
  function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32453
32659
  const { themeClass, colors: colors2, fonts, components } = useTheme();
32454
32660
  const t = useI18n().t.withdrawModal;
32455
- const [searchQuery, setSearchQuery] = (0, import_react40.useState)("");
32456
- const [hoveredKey, setHoveredKey] = (0, import_react40.useState)(null);
32457
- const allOptions = (0, import_react40.useMemo)(() => {
32661
+ const [searchQuery, setSearchQuery] = (0, import_react39.useState)("");
32662
+ const [hoveredKey, setHoveredKey] = (0, import_react39.useState)(null);
32663
+ const allOptions = (0, import_react39.useMemo)(() => {
32458
32664
  const options = [];
32459
32665
  tokens.forEach((token) => {
32460
32666
  token.chains.forEach((chain) => {
@@ -32463,7 +32669,7 @@ function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32463
32669
  });
32464
32670
  return options;
32465
32671
  }, [tokens]);
32466
- const fuse = (0, import_react40.useMemo)(
32672
+ const fuse = (0, import_react39.useMemo)(
32467
32673
  () => new import_fuse2.default(allOptions, {
32468
32674
  keys: [
32469
32675
  { name: "token.symbol", weight: 2 },
@@ -32476,7 +32682,7 @@ function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32476
32682
  }),
32477
32683
  [allOptions]
32478
32684
  );
32479
- const filteredOptions = (0, import_react40.useMemo)(() => {
32685
+ const filteredOptions = (0, import_react39.useMemo)(() => {
32480
32686
  if (!searchQuery.trim()) return allOptions;
32481
32687
  const query = searchQuery.trim();
32482
32688
  const results = fuse.search(query);