@unifold/ui-react 0.1.77 → 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
 
@@ -5589,7 +5619,11 @@ function BuyWithCard({
5589
5619
  method: "card",
5590
5620
  flow_type: "deposit",
5591
5621
  provider: selectedProvider.service_provider,
5592
- 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
5593
5627
  });
5594
5628
  onEvent?.({
5595
5629
  id: (0, import_core9.generatePrefixedKSUID)("sevt"),
@@ -7090,6 +7124,8 @@ var BuyWithWalletPay = React8.forwardRef(
7090
7124
  const [limitUpgradeError, setLimitUpgradeError] = (0, import_react15.useState)(null);
7091
7125
  const [upgradeSubmitting, setUpgradeSubmitting] = (0, import_react15.useState)(false);
7092
7126
  const upgradeFlowAbortRef = (0, import_react15.useRef)(null);
7127
+ const applyLimitRoutingRef = (0, import_react15.useRef)(() => {
7128
+ });
7093
7129
  const [amount, setAmount] = (0, import_react15.useState)(amountUsd ?? "");
7094
7130
  const [preparedOrder, setPreparedOrder] = (0, import_react15.useState)(null);
7095
7131
  const sessionAbortRef = (0, import_react15.useRef)(null);
@@ -7224,6 +7260,14 @@ var BuyWithWalletPay = React8.forwardRef(
7224
7260
  },
7225
7261
  [email, normalizedPhone, userId]
7226
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
+ });
7227
7271
  const createSessionAndSendOtp = (0, import_react15.useCallback)(async () => {
7228
7272
  setView("submitting_session");
7229
7273
  setErrorMessage("");
@@ -7233,7 +7277,12 @@ var BuyWithWalletPay = React8.forwardRef(
7233
7277
  email,
7234
7278
  phone: normalizedPhone,
7235
7279
  terms_accepted: true,
7236
- 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.
7237
7286
  },
7238
7287
  publishableKey
7239
7288
  );
@@ -7243,6 +7292,34 @@ var BuyWithWalletPay = React8.forwardRef(
7243
7292
  }
7244
7293
  setClientSecret(session.client_secret);
7245
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
+ }
7246
7323
  await (0, import_core15.sendOnrampVerificationOtp)(session.id, "phone", session.client_secret, publishableKey);
7247
7324
  setResendCooldown(30);
7248
7325
  setView("phone_otp");
@@ -7251,19 +7328,19 @@ var BuyWithWalletPay = React8.forwardRef(
7251
7328
  setErrorMessage(prettifyVerificationError(raw));
7252
7329
  setView("phone_input");
7253
7330
  }
7254
- }, [email, normalizedPhone, publishableKey, storeWalletPaySession]);
7331
+ }, [
7332
+ email,
7333
+ normalizedPhone,
7334
+ publishableKey,
7335
+ storeWalletPaySession,
7336
+ userId,
7337
+ amountUsd,
7338
+ refetchWalletPayLimits
7339
+ ]);
7255
7340
  const clearUpgradeFields = (0, import_react15.useCallback)(() => {
7256
7341
  setSsnLast4("");
7257
7342
  setDobInput("");
7258
7343
  }, []);
7259
- const { action: initialAction, refetchWalletPayLimits } = useWalletPayInitialScreen({
7260
- userId,
7261
- publishableKey,
7262
- normalizedPhone,
7263
- userEmail,
7264
- sessionPhoneVerified: verificationSession?.phone?.status === "verified",
7265
- isWaiting: defaultTokenLoading || legalAgreementsLoading || userIpInfoLoading
7266
- });
7267
7344
  const pollLimitUpgradeStatus = (0, import_react15.useCallback)(
7268
7345
  async (signal) => {
7269
7346
  const POLL_INTERVAL_MS5 = 1500;
@@ -7374,6 +7451,7 @@ var BuyWithWalletPay = React8.forwardRef(
7374
7451
  },
7375
7452
  [startUpgradePolling]
7376
7453
  );
7454
+ applyLimitRoutingRef.current = applyLimitRouting;
7377
7455
  const hasInitializedRef = (0, import_react15.useRef)(false);
7378
7456
  (0, import_react15.useEffect)(() => {
7379
7457
  if (hasInitializedRef.current) return;
@@ -7603,7 +7681,10 @@ var BuyWithWalletPay = React8.forwardRef(
7603
7681
  track("flow_started", {
7604
7682
  method,
7605
7683
  flow_type: "deposit",
7606
- 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
7607
7688
  });
7608
7689
  onEvent?.({
7609
7690
  id: (0, import_core15.generatePrefixedKSUID)("sevt"),
@@ -7653,6 +7734,7 @@ var BuyWithWalletPay = React8.forwardRef(
7653
7734
  defaultTokenLoading,
7654
7735
  defaultTokenError,
7655
7736
  depositWallet?.address,
7737
+ depositWalletId,
7656
7738
  userEmail,
7657
7739
  publishableKey,
7658
7740
  onEvent,
@@ -8627,7 +8709,7 @@ var FALLBACK_AGREEMENTS = [
8627
8709
  {
8628
8710
  key: "guest_checkout_terms",
8629
8711
  name: "Guest Checkout Terms of Service",
8630
- url: "https://www.coinbase.com/legal/cb-pay/onramp-guest-checkout-terms"
8712
+ url: "https://www.coinbase.com/legal/guest-checkout/us"
8631
8713
  },
8632
8714
  {
8633
8715
  key: "user_agreement",
@@ -8647,22 +8729,24 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8647
8729
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
8648
8730
  "div",
8649
8731
  {
8650
- 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",
8651
8733
  "aria-hidden": true,
8652
8734
  "aria-busy": "true",
8653
8735
  children: [
8654
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2.5 uf-rounded uf-w-full", style: { backgroundColor: colors2.card } }),
8655
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "uf-h-2.5 uf-rounded uf-w-11/12", style: { backgroundColor: colors2.card } }),
8656
- /* @__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 } })
8657
8739
  ]
8658
8740
  }
8659
8741
  );
8660
8742
  }
8661
8743
  const agreements = legalAgreements?.agreements ?? FALLBACK_AGREEMENTS;
8744
+ const shortLabels = t.agreements;
8745
+ const lastSeparator = t.lastSeparator.replace(/\s+$/, "\xA0");
8662
8746
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
8663
8747
  "p",
8664
8748
  {
8665
- 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",
8666
8750
  style: { color: colors2.foregroundMuted, fontFamily: fonts.regular },
8667
8751
  children: [
8668
8752
  t.prefix,
@@ -8673,12 +8757,12 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8673
8757
  href: a.url,
8674
8758
  target: "_blank",
8675
8759
  rel: "noopener noreferrer",
8676
- className: "uf-underline",
8677
- style: { color: colors2.foreground },
8678
- children: a.name
8760
+ className: "uf-no-underline",
8761
+ style: { color: "inherit", textDecoration: "none", whiteSpace: "nowrap" },
8762
+ children: shortLabels[a.key] ?? a.name
8679
8763
  }
8680
8764
  ),
8681
- idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? t.lastSeparator : ""
8765
+ idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? lastSeparator : ""
8682
8766
  ] }, a.key)),
8683
8767
  t.suffix
8684
8768
  ]
@@ -9350,6 +9434,7 @@ function PayWithCashApp({
9350
9434
  destinationChainType,
9351
9435
  destinationChainId,
9352
9436
  destinationTokenAddress,
9437
+ contractCalls,
9353
9438
  cashAppIconUrl,
9354
9439
  view: controlledView,
9355
9440
  onViewChange,
@@ -9442,7 +9527,8 @@ function PayWithCashApp({
9442
9527
  destination_token_address: destinationTokenAddress,
9443
9528
  recipient_address: recipientAddress,
9444
9529
  source_amount: amount,
9445
- external_id: externalId
9530
+ external_id: externalId,
9531
+ ...contractCalls?.length ? { contract_calls: contractCalls } : {}
9446
9532
  },
9447
9533
  publishableKey
9448
9534
  );
@@ -9455,7 +9541,10 @@ function PayWithCashApp({
9455
9541
  track("flow_started", {
9456
9542
  method: "cashapp",
9457
9543
  flow_type: "deposit",
9458
- 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
9459
9548
  });
9460
9549
  onEvent?.({
9461
9550
  id: (0, import_core19.generatePrefixedKSUID)("sevt"),
@@ -9480,6 +9569,8 @@ function PayWithCashApp({
9480
9569
  destinationChainType,
9481
9570
  destinationChainId,
9482
9571
  destinationTokenAddress,
9572
+ contractCalls,
9573
+ depositWalletId,
9483
9574
  onEvent,
9484
9575
  onDepositError,
9485
9576
  track,
@@ -10093,7 +10184,10 @@ function BankTransfer({
10093
10184
  method: "bank_transfer",
10094
10185
  flow_type: "deposit",
10095
10186
  provider: request.service_provider,
10096
- 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
10097
10191
  });
10098
10192
  onEvent?.({
10099
10193
  id: (0, import_core21.generatePrefixedKSUID)("sevt"),
@@ -16333,7 +16427,11 @@ function PayWithStripeLink({
16333
16427
  if (checkoutResult.successful) {
16334
16428
  handledTerminalSessionRef.current = null;
16335
16429
  executionReconciledSessionRef.current = null;
16336
- 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
+ });
16337
16435
  setConfirmedSessionId(activeSessionId);
16338
16436
  setSessionStatus(STRIPE_SESSION_STATUS.FULFILLMENT_PROCESSING);
16339
16437
  if (selectedTokenSingleUseRef.current) clearSelectedPaymentToken();
@@ -17830,7 +17928,14 @@ function PayWithStripeLink({
17830
17928
  ]
17831
17929
  }
17832
17930
  ),
17833
- /* @__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
+ )
17834
17939
  ]
17835
17940
  }
17836
17941
  );
@@ -19111,6 +19216,9 @@ function CoinbaseConnect({
19111
19216
  flow_type: "deposit",
19112
19217
  method: "exchange_connect",
19113
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,
19114
19222
  amount_usd: sendAmount || void 0,
19115
19223
  currency: selectedAsset?.currency.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
19116
19224
  chain: sourceChain?.chain_id,
@@ -19118,6 +19226,34 @@ function CoinbaseConnect({
19118
19226
  network: sourceChain?.network
19119
19227
  });
19120
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
+ };
19121
19257
  const handleConfirmTransfer = async () => {
19122
19258
  if (!transferIntent || !accessToken) return;
19123
19259
  setIsLoading(true);
@@ -19152,30 +19288,7 @@ function CoinbaseConnect({
19152
19288
  });
19153
19289
  throw err;
19154
19290
  });
19155
- if (result.status === "mfa_required") {
19156
- track("verification_started", {
19157
- flow_type: "deposit",
19158
- method: "exchange_connect",
19159
- provider: import_core27.IntegrationProvider.COINBASE,
19160
- verification_type: "mfa",
19161
- verification_status: result.status
19162
- });
19163
- transitionTo("mfa");
19164
- setMfaCode("");
19165
- setMfaError(false);
19166
- } else if (result.status === "completed") {
19167
- trackTransferStarted();
19168
- setConfirmResult(result);
19169
- transitionTo("success");
19170
- onTransferSuccess?.(result);
19171
- } else {
19172
- setErrorMessage(result.message || t.errors.transferFailed);
19173
- transitionTo("error");
19174
- onTransferError?.({
19175
- message: result.message || t.errors.transferFailed,
19176
- code: result.status
19177
- });
19178
- }
19291
+ applyConfirmResult(result);
19179
19292
  } catch (err) {
19180
19293
  const message = err instanceof Error ? err.message : t.errors.transferFailed;
19181
19294
  setErrorMessage(message);
@@ -19332,6 +19445,9 @@ function CoinbaseConnect({
19332
19445
  setShowTransferDetails(false);
19333
19446
  transitionTo("enter_amount");
19334
19447
  break;
19448
+ case "mfa_enrollment":
19449
+ transitionTo("transfer_confirm");
19450
+ break;
19335
19451
  case "mfa":
19336
19452
  transitionTo("transfer_confirm");
19337
19453
  break;
@@ -20888,6 +21004,55 @@ function CoinbaseConnect({
20888
21004
  ] })
20889
21005
  ] });
20890
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
+ }
20891
21056
  if (view === "mfa") {
20892
21057
  return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
20893
21058
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(DepositHeader, { title: t.title, showBack: true, onBack: handleBack, onClose }),
@@ -26994,7 +27159,11 @@ function WalletConnect({
26994
27159
  chain: String(token.chain_id),
26995
27160
  network: token.network,
26996
27161
  token: token.symbol ?? token.token_address,
26997
- 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
26998
27167
  });
26999
27168
  setReceivedUsdAtSubmission(checkoutReceivedUsd ?? "0");
27000
27169
  setHasSignedTransaction(true);
@@ -27584,7 +27753,7 @@ function DepositModal({
27584
27753
  const applePaySubTitle = applePaySubTitleProp ?? t.applePayMenu.subtitle;
27585
27754
  const googlePayTitle = googlePayTitleProp ?? t.googlePayMenu.title;
27586
27755
  const googlePaySubTitle = googlePaySubTitleProp ?? t.googlePayMenu.subtitle;
27587
- const { track, startSession, endSession, getSessionId, subscribe } = useAnalytics();
27756
+ const { track, startSession, endSession, getSessionId, setUserId, subscribe } = useAnalytics();
27588
27757
  const normalizedPrefilledAmountUsd = (0, import_react31.useMemo)(
27589
27758
  () => normalizePrefilledUsdAmount(prefilledAmountUsd),
27590
27759
  [prefilledAmountUsd]
@@ -27592,6 +27761,7 @@ function DepositModal({
27592
27761
  const analyticsMethod = (method) => method === "transfer" ? "transfer_crypto" : method;
27593
27762
  const journeyActiveRef = (0, import_react31.useRef)(false);
27594
27763
  const journeySessionIdRef = (0, import_react31.useRef)(void 0);
27764
+ const journeyExternalUserIdRef = (0, import_react31.useRef)(void 0);
27595
27765
  const externalUserId = userId || void 0;
27596
27766
  const emitEvent = (0, import_react31.useCallback)(
27597
27767
  (event) => onEvent?.({ ...event, externalUserId }),
@@ -27619,6 +27789,9 @@ function DepositModal({
27619
27789
  method: analyticsMethod(method),
27620
27790
  flow_type: "deposit",
27621
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,
27622
27795
  // Flat, low-cardinality execution dimensions for PostHog breakdowns
27623
27796
  // (precise/opaque fields like base units, addresses, tx hashes live in
27624
27797
  // the onEvent DirectExecution instead).
@@ -27646,6 +27819,7 @@ function DepositModal({
27646
27819
  method: analyticsMethod(method),
27647
27820
  flow_type: "deposit",
27648
27821
  error_code: error.code,
27822
+ execution_id: execution?.id,
27649
27823
  failure_reason: execution?.failureReason ?? void 0,
27650
27824
  status: execution?.status,
27651
27825
  source_chain_type: execution?.sourceChainType,
@@ -28074,8 +28248,10 @@ function DepositModal({
28074
28248
  const beginJourney = (0, import_react31.useCallback)(() => {
28075
28249
  journeySessionIdRef.current = startSession();
28076
28250
  journeyActiveRef.current = true;
28251
+ journeyExternalUserIdRef.current = userId;
28252
+ setUserId(null);
28077
28253
  track("widget_opened", { flow_type: "deposit" });
28078
- }, [startSession, track]);
28254
+ }, [startSession, track, setUserId, userId]);
28079
28255
  const handleClose = () => {
28080
28256
  finalizeJourney();
28081
28257
  onOpenChange(false);
@@ -28100,6 +28276,11 @@ function DepositModal({
28100
28276
  }
28101
28277
  beginJourney();
28102
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]);
28103
28284
  (0, import_react31.useLayoutEffect)(() => {
28104
28285
  if (!open) return;
28105
28286
  if (resetViewTimeoutRef.current) {
@@ -29011,6 +29192,7 @@ function DepositModal({
29011
29192
  destinationChainType,
29012
29193
  destinationChainId,
29013
29194
  destinationTokenAddress,
29195
+ contractCalls,
29014
29196
  cashAppIconUrl: projectConfig?.asset_cdn_url ? `${projectConfig.asset_cdn_url}/api/public/icons/onramps/svg/cashapp.svg` : void 0,
29015
29197
  view: cashAppView,
29016
29198
  onViewChange: setCashAppView,
@@ -29371,6 +29553,9 @@ function CheckoutModal({
29371
29553
  flowCompletedRef.current = true;
29372
29554
  track("flow_completed", {
29373
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,
29374
29559
  ...method ? { method: method === "transfer" ? "transfer_crypto" : method } : {}
29375
29560
  });
29376
29561
  }
@@ -29591,7 +29776,7 @@ function CheckoutModal({
29591
29776
  journeySessionIdRef.current = startSession();
29592
29777
  journeyActiveRef.current = true;
29593
29778
  flowCompletedRef.current = false;
29594
- setUserId(null);
29779
+ setUserId(paymentIntentUserId ?? null);
29595
29780
  track("widget_opened", { flow_type: "checkout" });
29596
29781
  if (resetViewTimeoutRef.current) {
29597
29782
  clearTimeout(resetViewTimeoutRef.current);
@@ -30032,7 +30217,7 @@ function CheckoutModal({
30032
30217
  }
30033
30218
 
30034
30219
  // src/components/withdrawals/WithdrawModal.tsx
30035
- var import_react39 = require("react");
30220
+ var import_react38 = require("react");
30036
30221
  var import_lucide_react44 = require("lucide-react");
30037
30222
 
30038
30223
  // src/hooks/use-supported-destination-tokens.ts
@@ -30198,7 +30383,7 @@ var import_react_query24 = require("@tanstack/react-query");
30198
30383
  var import_core50 = require("@unifold/core");
30199
30384
  function useExecutions(userId, publishableKey, options) {
30200
30385
  const actionType = options?.actionType ?? import_core50.ActionType.Deposit;
30201
- return (0, import_react_query24.useQuery)({
30386
+ const query = (0, import_react_query24.useQuery)({
30202
30387
  queryKey: ["unifold", "executions", actionType, userId, publishableKey],
30203
30388
  queryFn: () => (0, import_core50.queryExecutions)(userId, publishableKey, actionType),
30204
30389
  enabled: (options?.enabled ?? true) && !!userId,
@@ -30207,6 +30392,8 @@ function useExecutions(userId, publishableKey, options) {
30207
30392
  gcTime: 1e3 * 60 * 5,
30208
30393
  refetchOnWindowFocus: false
30209
30394
  });
30395
+ useStampInternalUserId({ internalUserId: query.data?.data[0]?.user_id });
30396
+ return query;
30210
30397
  }
30211
30398
 
30212
30399
  // src/hooks/use-withdraw-polling.ts
@@ -30541,7 +30728,7 @@ function WithdrawDoubleInput({
30541
30728
  }
30542
30729
 
30543
30730
  // src/components/withdrawals/WithdrawForm.tsx
30544
- var import_react37 = require("react");
30731
+ var import_react36 = require("react");
30545
30732
  var import_lucide_react42 = require("lucide-react");
30546
30733
  var import_core56 = require("@unifold/core");
30547
30734
 
@@ -30804,11 +30991,10 @@ async function detectBrowserWallet(chainType, senderAddress) {
30804
30991
  }
30805
30992
 
30806
30993
  // src/hooks/use-hypercore-withdraw-activation.ts
30807
- var import_react36 = require("react");
30994
+ var import_react35 = require("react");
30808
30995
  var import_core55 = require("@unifold/core");
30809
30996
 
30810
30997
  // src/hooks/use-get-deposit-address.ts
30811
- var import_react35 = require("react");
30812
30998
  var import_react_query26 = require("@tanstack/react-query");
30813
30999
  var import_core54 = require("@unifold/core");
30814
31000
  function useGetDepositAddress(params) {
@@ -30822,7 +31008,6 @@ function useGetDepositAddress(params) {
30822
31008
  actionType,
30823
31009
  enabled = true
30824
31010
  } = params;
30825
- const { setUserId } = useAnalytics();
30826
31011
  const canFire = !!userId && !!recipientAddress && !!destinationChainType && !!destinationChainId && !!destinationTokenAddress;
30827
31012
  const query = (0, import_react_query26.useQuery)({
30828
31013
  queryKey: [
@@ -30854,11 +31039,7 @@ function useGetDepositAddress(params) {
30854
31039
  refetchOnWindowFocus: false,
30855
31040
  retry: 1
30856
31041
  });
30857
- (0, import_react35.useEffect)(() => {
30858
- if (query.data?.user_id) {
30859
- setUserId(query.data.user_id);
30860
- }
30861
- }, [query.data?.user_id, setUserId]);
31042
+ useStampInternalUserId({ internalUserId: query.data?.user_id });
30862
31043
  return query;
30863
31044
  }
30864
31045
 
@@ -30899,7 +31080,7 @@ function useHypercoreWithdrawActivation(params) {
30899
31080
  actionType: import_core55.ActionType.Withdraw,
30900
31081
  enabled: enabled && isHypercore(sourceChainId)
30901
31082
  });
30902
- const depositWalletAddress = (0, import_react36.useMemo)(() => {
31083
+ const depositWalletAddress = (0, import_react35.useMemo)(() => {
30903
31084
  const wallets = depositWalletLookup.data?.data ?? [];
30904
31085
  return wallets.find((w) => w.chain_type === sourceChainType)?.address;
30905
31086
  }, [depositWalletLookup.data, sourceChainType]);
@@ -30990,22 +31171,22 @@ function WithdrawForm({
30990
31171
  const strings = useI18n().t;
30991
31172
  const t = strings.withdrawModal;
30992
31173
  const tCrypto = strings.transferCrypto;
30993
- const [recipientAddress, setRecipientAddress] = (0, import_react37.useState)(recipientAddressProp || "");
30994
- const [amount, setAmount] = (0, import_react37.useState)("");
30995
- const [inputUnit, setInputUnit] = (0, import_react37.useState)("fiat");
30996
- const [isSubmitting, setIsSubmitting] = (0, import_react37.useState)(false);
30997
- const [submitError, setSubmitError] = (0, import_react37.useState)(null);
30998
- const [detailsExpanded, setDetailsExpanded] = (0, import_react37.useState)(false);
30999
- const [glossaryOpen, setGlossaryOpen] = (0, import_react37.useState)(false);
31000
- const [isMaxed, setIsMaxed] = (0, import_react37.useState)(false);
31001
- (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)(() => {
31002
31183
  setRecipientAddress(recipientAddressProp || "");
31003
31184
  setAmount("");
31004
31185
  setInputUnit("fiat");
31005
31186
  setSubmitError(null);
31006
31187
  setIsMaxed(false);
31007
31188
  }, [recipientAddressProp]);
31008
- (0, import_react37.useEffect)(() => {
31189
+ (0, import_react36.useEffect)(() => {
31009
31190
  setIsMaxed(false);
31010
31191
  }, [balanceData?.balanceBaseUnit]);
31011
31192
  const trimmedAddress = recipientAddress.trim();
@@ -31023,7 +31204,7 @@ function WithdrawForm({
31023
31204
  enabled: debouncedAddress.length > 5 && !!selectedChain
31024
31205
  });
31025
31206
  const isDebouncing = trimmedAddress !== debouncedAddress;
31026
- const addressError = (0, import_react37.useMemo)(() => {
31207
+ const addressError = (0, import_react36.useMemo)(() => {
31027
31208
  if (!trimmedAddress || trimmedAddress.length <= 5) return null;
31028
31209
  if (isDebouncing || isVerifyingAddress) return null;
31029
31210
  if (verifyError) return t.invalidAddress;
@@ -31067,33 +31248,33 @@ function WithdrawForm({
31067
31248
  destinationTokenAddress: selectedChain?.token_address,
31068
31249
  enabled: isAddressValid
31069
31250
  });
31070
- const exchangeRate = (0, import_react37.useMemo)(() => {
31251
+ const exchangeRate = (0, import_react36.useMemo)(() => {
31071
31252
  if (!balanceData?.exchangeRate) return 0;
31072
31253
  return parseFloat(balanceData.exchangeRate);
31073
31254
  }, [balanceData]);
31074
- const balanceCrypto = (0, import_react37.useMemo)(() => {
31255
+ const balanceCrypto = (0, import_react36.useMemo)(() => {
31075
31256
  if (!balanceData?.balanceHuman) return 0;
31076
31257
  return parseFloat(balanceData.balanceHuman);
31077
31258
  }, [balanceData]);
31078
- const balanceUsdNum = (0, import_react37.useMemo)(() => {
31259
+ const balanceUsdNum = (0, import_react36.useMemo)(() => {
31079
31260
  if (!balanceData?.balanceUsd) return 0;
31080
31261
  return parseFloat(balanceData.balanceUsd);
31081
31262
  }, [balanceData]);
31082
31263
  const tokenSymbol = sourceTokenSymbol || balanceData?.symbol || "TOKEN";
31083
31264
  const sourceDecimals = balanceData?.decimals ?? 6;
31084
- const cryptoAmountFromInput = (0, import_react37.useMemo)(() => {
31265
+ const cryptoAmountFromInput = (0, import_react36.useMemo)(() => {
31085
31266
  const val = parseFloat(amount);
31086
31267
  if (!val || val <= 0) return 0;
31087
31268
  if (inputUnit === "crypto") return val;
31088
31269
  return exchangeRate > 0 ? val / exchangeRate : 0;
31089
31270
  }, [amount, inputUnit, exchangeRate]);
31090
- const fiatAmountFromInput = (0, import_react37.useMemo)(() => {
31271
+ const fiatAmountFromInput = (0, import_react36.useMemo)(() => {
31091
31272
  const val = parseFloat(amount);
31092
31273
  if (!val || val <= 0) return 0;
31093
31274
  if (inputUnit === "fiat") return val;
31094
31275
  return val * exchangeRate;
31095
31276
  }, [amount, inputUnit, exchangeRate]);
31096
- const convertedDisplay = (0, import_react37.useMemo)(() => {
31277
+ const convertedDisplay = (0, import_react36.useMemo)(() => {
31097
31278
  if (!amount || parseFloat(amount) <= 0) return null;
31098
31279
  if (inputUnit === "crypto") {
31099
31280
  return `$${fiatAmountFromInput.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
@@ -31112,7 +31293,7 @@ function WithdrawForm({
31112
31293
  isMaxed,
31113
31294
  isStablecoin
31114
31295
  ]);
31115
- const balanceDisplay = (0, import_react37.useMemo)(() => {
31296
+ const balanceDisplay = (0, import_react36.useMemo)(() => {
31116
31297
  if (isLoadingBalance || !balanceData) return null;
31117
31298
  if (inputUnit === "crypto") {
31118
31299
  const displayDecimals = isStablecoin ? 2 : 6;
@@ -31135,7 +31316,7 @@ function WithdrawForm({
31135
31316
  tokenSymbol,
31136
31317
  isStablecoin
31137
31318
  ]);
31138
- const handleSwitchUnit = (0, import_react37.useCallback)(() => {
31319
+ const handleSwitchUnit = (0, import_react36.useCallback)(() => {
31139
31320
  if (isMaxed && balanceData) {
31140
31321
  if (inputUnit === "crypto") {
31141
31322
  setAmount((Math.round(balanceUsdNum * 100) / 100).toFixed(2));
@@ -31162,7 +31343,7 @@ function WithdrawForm({
31162
31343
  setInputUnit("crypto");
31163
31344
  }
31164
31345
  }, [amount, inputUnit, exchangeRate, sourceDecimals, isMaxed, balanceData, balanceUsdNum]);
31165
- const handleMaxClick = (0, import_react37.useCallback)(() => {
31346
+ const handleMaxClick = (0, import_react36.useCallback)(() => {
31166
31347
  if (inputUnit === "crypto") {
31167
31348
  if (balanceCrypto <= 0) return;
31168
31349
  setAmount(balanceData?.balanceHuman ?? "0");
@@ -31176,7 +31357,7 @@ function WithdrawForm({
31176
31357
  const isBelowMinimum = minimumWithdrawAmountUsd !== null && fiatAmountFromInput > 0 && Math.round(fiatAmountFromInput * 100) / 100 < minimumWithdrawAmountUsd;
31177
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;
31178
31359
  const isFormValid = trimmedAddress.length > 0 && amount.trim().length > 0 && cryptoAmountFromInput > 0 && isAddressValid && !isBelowMinimum && !isOverBalance && !isBalanceBelowMinimum && !!balanceData;
31179
- const handleWithdraw = (0, import_react37.useCallback)(async () => {
31360
+ const handleWithdraw = (0, import_react36.useCallback)(async () => {
31180
31361
  if (!selectedToken || !selectedChain) return;
31181
31362
  if (!isFormValid) return;
31182
31363
  setIsSubmitting(true);
@@ -31806,7 +31987,7 @@ function WithdrawExecutionItem({ execution, onClick }) {
31806
31987
  }
31807
31988
 
31808
31989
  // src/components/withdrawals/WithdrawConfirmingView.tsx
31809
- var import_react38 = require("react");
31990
+ var import_react37 = require("react");
31810
31991
  var import_jsx_runtime76 = require("react/jsx-runtime");
31811
31992
  function truncateAddress4(addr) {
31812
31993
  if (addr.length <= 12) return addr;
@@ -31822,9 +32003,9 @@ function WithdrawConfirmingView({
31822
32003
  const { colors: colors2, fonts, components } = useTheme();
31823
32004
  const { t: strings } = useI18n();
31824
32005
  const t = strings.withdrawModal.details;
31825
- const [showButton, setShowButton] = (0, import_react38.useState)(false);
32006
+ const [showButton, setShowButton] = (0, import_react37.useState)(false);
31826
32007
  const latestExecution = executions.length > 0 ? executions[executions.length - 1] : null;
31827
- (0, import_react38.useEffect)(() => {
32008
+ (0, import_react37.useEffect)(() => {
31828
32009
  if (latestExecution) return;
31829
32010
  const timer = setTimeout(() => setShowButton(true), SHOW_BUTTON_DELAY_MS);
31830
32011
  return () => clearTimeout(timer);
@@ -31974,13 +32155,13 @@ function WithdrawModal({
31974
32155
  const { colors: colors2, fonts, components } = useTheme();
31975
32156
  const t = useI18n().t.withdrawModal;
31976
32157
  const { track, startSession, endSession, getSessionId, setUserId, subscribe } = useAnalytics();
31977
- const emitEvent = (0, import_react39.useCallback)(
32158
+ const emitEvent = (0, import_react38.useCallback)(
31978
32159
  (event) => onEvent?.({ ...event, externalUserId }),
31979
32160
  [onEvent, externalUserId]
31980
32161
  );
31981
- const onEventRef = (0, import_react39.useRef)(emitEvent);
32162
+ const onEventRef = (0, import_react38.useRef)(emitEvent);
31982
32163
  onEventRef.current = emitEvent;
31983
- (0, import_react39.useEffect)(
32164
+ (0, import_react38.useEffect)(
31984
32165
  () => subscribe((event) => {
31985
32166
  if (event.properties?.flow_type !== "withdraw") return;
31986
32167
  const selection = selectionPayload(event);
@@ -31995,14 +32176,18 @@ function WithdrawModal({
31995
32176
  }),
31996
32177
  [subscribe, getSessionId]
31997
32178
  );
31998
- const journeyActiveRef = (0, import_react39.useRef)(false);
31999
- const journeySessionIdRef = (0, import_react39.useRef)(void 0);
32000
- 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)(
32001
32183
  (data) => {
32002
32184
  const execution = data.execution?.data.object;
32003
32185
  track("flow_completed", {
32004
32186
  flow_type: "withdraw",
32005
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,
32006
32191
  // Flat, low-cardinality execution dimensions for PostHog breakdowns.
32007
32192
  status: execution?.status,
32008
32193
  source_chain_type: execution?.sourceChainType,
@@ -32017,12 +32202,13 @@ function WithdrawModal({
32017
32202
  },
32018
32203
  [onWithdrawSuccess, emitEvent, track]
32019
32204
  );
32020
- const onWithdrawErrorFor = (0, import_react39.useCallback)(
32205
+ const onWithdrawErrorFor = (0, import_react38.useCallback)(
32021
32206
  (error) => {
32022
32207
  const execution = error.execution?.data.object;
32023
32208
  track("flow_failed", {
32024
32209
  flow_type: "withdraw",
32025
32210
  error_code: error.code,
32211
+ execution_id: execution?.id,
32026
32212
  failure_reason: execution?.failureReason ?? void 0,
32027
32213
  status: execution?.status,
32028
32214
  source_chain_type: execution?.sourceChainType,
@@ -32037,14 +32223,14 @@ function WithdrawModal({
32037
32223
  },
32038
32224
  [onWithdrawError, emitEvent, track]
32039
32225
  );
32040
- const [containerEl, setContainerEl] = (0, import_react39.useState)(null);
32041
- const containerCallbackRef = (0, import_react39.useCallback)((el) => {
32226
+ const [containerEl, setContainerEl] = (0, import_react38.useState)(null);
32227
+ const containerCallbackRef = (0, import_react38.useCallback)((el) => {
32042
32228
  setContainerEl(el);
32043
32229
  }, []);
32044
- const [resolvedTheme, setResolvedTheme] = (0, import_react39.useState)(
32230
+ const [resolvedTheme, setResolvedTheme] = (0, import_react38.useState)(
32045
32231
  theme === "auto" ? "dark" : theme
32046
32232
  );
32047
- (0, import_react39.useEffect)(() => {
32233
+ (0, import_react38.useEffect)(() => {
32048
32234
  if (theme === "auto") {
32049
32235
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
32050
32236
  setResolvedTheme(mq.matches ? "dark" : "light");
@@ -32059,7 +32245,7 @@ function WithdrawModal({
32059
32245
  publishableKey,
32060
32246
  open
32061
32247
  );
32062
- const destinationTokens = (0, import_react39.useMemo)(() => tokensResponse?.data ?? [], [tokensResponse]);
32248
+ const destinationTokens = (0, import_react38.useMemo)(() => tokensResponse?.data ?? [], [tokensResponse]);
32063
32249
  const { data: sourceValidation, isLoading: isCheckingSourceToken } = useSourceTokenValidation({
32064
32250
  sourceChainType,
32065
32251
  sourceChainId,
@@ -32094,10 +32280,11 @@ function WithdrawModal({
32094
32280
  const selectedChain = selectedToken && selectedChainKey ? selectedToken.chains.find(
32095
32281
  (c) => getChainKey5(c.chain_id, c.chain_type) === selectedChainKey
32096
32282
  ) ?? null : null;
32097
- const [view, setView] = (0, import_react39.useState)("form");
32098
- const [withdrawDepositWalletId, setWithdrawDepositWalletId] = (0, import_react39.useState)();
32099
- const [selectedExecution, setSelectedExecution] = (0, import_react39.useState)(null);
32100
- 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);
32101
32288
  const { executions: realtimeExecutions } = useWithdrawPolling({
32102
32289
  userId: externalUserId,
32103
32290
  publishableKey,
@@ -32112,7 +32299,7 @@ function WithdrawModal({
32112
32299
  refetchInterval: view === "tracker" || view === "detail" ? 5e3 : 15e3
32113
32300
  });
32114
32301
  const allWithdrawals = allWithdrawalsData?.data ?? [];
32115
- const handleDepositWalletCreation = (0, import_react39.useCallback)(
32302
+ const handleDepositWalletCreation = (0, import_react38.useCallback)(
32116
32303
  async (params) => {
32117
32304
  const response = await (0, import_core58.createDepositAddress)(
32118
32305
  {
@@ -32133,12 +32320,13 @@ function WithdrawModal({
32133
32320
  if (!depositWallet) {
32134
32321
  throw new Error(`No deposit wallet available for ${sourceChainType}`);
32135
32322
  }
32323
+ withdrawDepositWalletIdRef.current = depositWallet.id;
32136
32324
  setWithdrawDepositWalletId(depositWallet.id);
32137
32325
  return depositWallet;
32138
32326
  },
32139
32327
  [externalUserId, publishableKey, sourceChainType, setUserId]
32140
32328
  );
32141
- const handleWithdrawStarted = (0, import_react39.useCallback)(
32329
+ const handleWithdrawStarted = (0, import_react38.useCallback)(
32142
32330
  (txInfo) => {
32143
32331
  const tokenData = destinationTokens.find((t2) => t2.symbol === txInfo.destinationTokenSymbol);
32144
32332
  const chainData = tokenData?.chains.find(
@@ -32152,7 +32340,12 @@ function WithdrawModal({
32152
32340
  currency: tokenData?.currency,
32153
32341
  amount: txInfo.amount,
32154
32342
  amount_base_unit: txInfo.amountBaseUnit,
32155
- ...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
32156
32349
  });
32157
32350
  emitEvent({
32158
32351
  id: (0, import_core58.generatePrefixedKSUID)("sevt"),
@@ -32172,23 +32365,25 @@ function WithdrawModal({
32172
32365
  },
32173
32366
  [track, emitEvent, getSessionId, destinationTokens]
32174
32367
  );
32175
- const handleWithdrawSubmitted = (0, import_react39.useCallback)((txInfo) => {
32368
+ const handleWithdrawSubmitted = (0, import_react38.useCallback)((txInfo) => {
32176
32369
  setSubmittedTxInfo(txInfo);
32177
32370
  setView("confirming");
32178
32371
  }, []);
32179
- const resetViewTimeoutRef = (0, import_react39.useRef)(null);
32180
- const finalizeJourney = (0, import_react39.useCallback)(() => {
32372
+ const resetViewTimeoutRef = (0, import_react38.useRef)(null);
32373
+ const finalizeJourney = (0, import_react38.useCallback)(() => {
32181
32374
  if (!journeyActiveRef.current) return;
32182
32375
  journeyActiveRef.current = false;
32183
32376
  track("widget_closed", { flow_type: "withdraw" });
32184
32377
  if (getSessionId() === journeySessionIdRef.current) endSession();
32185
32378
  }, [track, getSessionId, endSession]);
32186
- const beginJourney = (0, import_react39.useCallback)(() => {
32379
+ const beginJourney = (0, import_react38.useCallback)(() => {
32187
32380
  journeySessionIdRef.current = startSession();
32188
32381
  journeyActiveRef.current = true;
32382
+ journeyExternalUserIdRef.current = externalUserId;
32383
+ setUserId(null);
32189
32384
  track("widget_opened", { flow_type: "withdraw" });
32190
- }, [startSession, track]);
32191
- const handleClose = (0, import_react39.useCallback)(() => {
32385
+ }, [startSession, track, setUserId, externalUserId]);
32386
+ const handleClose = (0, import_react38.useCallback)(() => {
32192
32387
  finalizeJourney();
32193
32388
  onOpenChange(false);
32194
32389
  if (resetViewTimeoutRef.current) clearTimeout(resetViewTimeoutRef.current);
@@ -32196,11 +32391,12 @@ function WithdrawModal({
32196
32391
  setView("form");
32197
32392
  setSelectedExecution(null);
32198
32393
  setSubmittedTxInfo(null);
32394
+ withdrawDepositWalletIdRef.current = void 0;
32199
32395
  setWithdrawDepositWalletId(void 0);
32200
32396
  resetViewTimeoutRef.current = null;
32201
32397
  }, 200);
32202
32398
  }, [onOpenChange, finalizeJourney]);
32203
- (0, import_react39.useLayoutEffect)(() => {
32399
+ (0, import_react38.useLayoutEffect)(() => {
32204
32400
  if (!open) {
32205
32401
  finalizeJourney();
32206
32402
  return;
@@ -32213,9 +32409,15 @@ function WithdrawModal({
32213
32409
  setView("form");
32214
32410
  setSelectedExecution(null);
32215
32411
  setSubmittedTxInfo(null);
32412
+ withdrawDepositWalletIdRef.current = void 0;
32216
32413
  setWithdrawDepositWalletId(void 0);
32217
32414
  }, [open]);
32218
- (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)(
32219
32421
  () => () => {
32220
32422
  if (journeyActiveRef.current) {
32221
32423
  journeyActiveRef.current = false;
@@ -32224,18 +32426,18 @@ function WithdrawModal({
32224
32426
  },
32225
32427
  [endSession, getSessionId]
32226
32428
  );
32227
- (0, import_react39.useEffect)(() => {
32429
+ (0, import_react38.useEffect)(() => {
32228
32430
  if (!open) return;
32229
32431
  const screen = view === "detail" ? "tracker_detail" : view;
32230
32432
  track("screen_viewed", { flow_type: "withdraw", screen });
32231
32433
  }, [open, view, track]);
32232
- (0, import_react39.useEffect)(
32434
+ (0, import_react38.useEffect)(
32233
32435
  () => () => {
32234
32436
  if (resetViewTimeoutRef.current) clearTimeout(resetViewTimeoutRef.current);
32235
32437
  },
32236
32438
  []
32237
32439
  );
32238
- const trackAssetSelected = (0, import_react39.useCallback)(
32440
+ const trackAssetSelected = (0, import_react38.useCallback)(
32239
32441
  (symbol, chainKey) => {
32240
32442
  const [chainType, chainId] = (chainKey ?? "").split(":");
32241
32443
  const tokenData = destinationTokens.find((t2) => t2.symbol === symbol);
@@ -32253,14 +32455,14 @@ function WithdrawModal({
32253
32455
  },
32254
32456
  [destinationTokens, track]
32255
32457
  );
32256
- const handleTokenSymbolChange = (0, import_react39.useCallback)(
32458
+ const handleTokenSymbolChange = (0, import_react38.useCallback)(
32257
32459
  (symbol) => {
32258
32460
  trackAssetSelected(symbol, selectedChainKey);
32259
32461
  setSelectedTokenSymbol(symbol);
32260
32462
  },
32261
32463
  [setSelectedTokenSymbol, selectedChainKey, trackAssetSelected]
32262
32464
  );
32263
- const handleChainKeyChange = (0, import_react39.useCallback)(
32465
+ const handleChainKeyChange = (0, import_react38.useCallback)(
32264
32466
  (chainKey) => {
32265
32467
  trackAssetSelected(selectedTokenSymbol, chainKey);
32266
32468
  setSelectedChainKey(chainKey);
@@ -32449,16 +32651,16 @@ function WithdrawModal({
32449
32651
  }
32450
32652
 
32451
32653
  // src/components/withdrawals/WithdrawTokenSelector.tsx
32452
- var import_react40 = require("react");
32654
+ var import_react39 = require("react");
32453
32655
  var import_lucide_react45 = require("lucide-react");
32454
32656
  var import_fuse2 = __toESM(require("fuse.js"));
32455
32657
  var import_jsx_runtime78 = require("react/jsx-runtime");
32456
32658
  function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32457
32659
  const { themeClass, colors: colors2, fonts, components } = useTheme();
32458
32660
  const t = useI18n().t.withdrawModal;
32459
- const [searchQuery, setSearchQuery] = (0, import_react40.useState)("");
32460
- const [hoveredKey, setHoveredKey] = (0, import_react40.useState)(null);
32461
- 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)(() => {
32462
32664
  const options = [];
32463
32665
  tokens.forEach((token) => {
32464
32666
  token.chains.forEach((chain) => {
@@ -32467,7 +32669,7 @@ function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32467
32669
  });
32468
32670
  return options;
32469
32671
  }, [tokens]);
32470
- const fuse = (0, import_react40.useMemo)(
32672
+ const fuse = (0, import_react39.useMemo)(
32471
32673
  () => new import_fuse2.default(allOptions, {
32472
32674
  keys: [
32473
32675
  { name: "token.symbol", weight: 2 },
@@ -32480,7 +32682,7 @@ function WithdrawTokenSelector({ tokens, onSelect, onBack }) {
32480
32682
  }),
32481
32683
  [allOptions]
32482
32684
  );
32483
- const filteredOptions = (0, import_react40.useMemo)(() => {
32685
+ const filteredOptions = (0, import_react39.useMemo)(() => {
32484
32686
  if (!searchQuery.trim()) return allOptions;
32485
32687
  const query = searchQuery.trim();
32486
32688
  const results = fuse.search(query);