@unifold/ui-react 0.1.77 → 0.1.79

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.mjs CHANGED
@@ -420,6 +420,9 @@ var en_default = {
420
420
  transactionBreakdown: "Transaction breakdown",
421
421
  networkFee: "Network fee",
422
422
  confirmOrder: "Confirm Order",
423
+ mfaEnrollmentTitle: "Enable SMS or Authy 2FA",
424
+ mfaEnrollmentMessage: "Please make sure you enable SMS or Authy 2FA in your Coinbase account. Press Continue after you enable it.",
425
+ mfaEnrollmentSettingsLink: "Open Coinbase security settings",
423
426
  termsNoticePrefix: "By clicking on Confirm Order, you agree to our ",
424
427
  termsNoticeLink: "terms",
425
428
  termsNoticeSuffix: "."
@@ -550,7 +553,12 @@ var en_default = {
550
553
  prefix: "By continuing, you agree to Coinbase's ",
551
554
  separator: ", ",
552
555
  lastSeparator: ", and ",
553
- suffix: ". Coinbase is the regulated provider of this fiat-to-crypto onramp."
556
+ suffix: ".",
557
+ agreements: {
558
+ guest_checkout_terms: "Terms of Service",
559
+ user_agreement: "User Agreement",
560
+ privacy_policy: "Privacy Policy"
561
+ }
554
562
  },
555
563
  errors: {
556
564
  rateLimited: "Too many attempts. Please wait a few minutes and try again.",
@@ -1211,6 +1219,9 @@ var zh_default = {
1211
1219
  transactionBreakdown: "\u4EA4\u6613\u660E\u7EC6",
1212
1220
  networkFee: "\u7F51\u7EDC\u624B\u7EED\u8D39",
1213
1221
  confirmOrder: "\u786E\u8BA4\u8BA2\u5355",
1222
+ mfaEnrollmentTitle: "\u8BF7\u542F\u7528\u77ED\u4FE1\u6216 Authy \u53CC\u91CD\u9A8C\u8BC1",
1223
+ 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",
1224
+ mfaEnrollmentSettingsLink: "\u6253\u5F00 Coinbase \u5B89\u5168\u8BBE\u7F6E",
1214
1225
  termsNoticePrefix: "\u70B9\u51FB\u201C\u786E\u8BA4\u8BA2\u5355\u201D\u5373\u8868\u793A\u60A8\u540C\u610F\u6211\u4EEC\u7684",
1215
1226
  termsNoticeLink: "\u6761\u6B3E",
1216
1227
  termsNoticeSuffix: "\u3002"
@@ -1341,7 +1352,12 @@ var zh_default = {
1341
1352
  prefix: "\u7EE7\u7EED\u5373\u8868\u793A\u60A8\u540C\u610F Coinbase \u7684",
1342
1353
  separator: "\u3001",
1343
1354
  lastSeparator: "\u548C",
1344
- suffix: "\u3002Coinbase \u662F\u672C\u6CD5\u5E01\u5151\u52A0\u5BC6\u8D27\u5E01\u5145\u503C\u670D\u52A1\u7684\u53D7\u76D1\u7BA1\u63D0\u4F9B\u65B9\u3002"
1355
+ suffix: "\u3002",
1356
+ agreements: {
1357
+ guest_checkout_terms: "\u670D\u52A1\u6761\u6B3E",
1358
+ user_agreement: "\u7528\u6237\u534F\u8BAE",
1359
+ privacy_policy: "\u9690\u79C1\u653F\u7B56"
1360
+ }
1345
1361
  },
1346
1362
  errors: {
1347
1363
  rateLimited: "\u5C1D\u8BD5\u6B21\u6570\u8FC7\u591A\u3002\u8BF7\u7A0D\u7B49\u51E0\u5206\u949F\u540E\u91CD\u8BD5\u3002",
@@ -2002,6 +2018,9 @@ var zh_TW_default = {
2002
2018
  transactionBreakdown: "\u4EA4\u6613\u660E\u7D30",
2003
2019
  networkFee: "\u7DB2\u8DEF\u624B\u7E8C\u8CBB",
2004
2020
  confirmOrder: "\u78BA\u8A8D\u8A02\u55AE",
2021
+ mfaEnrollmentTitle: "\u8ACB\u555F\u7528\u7C21\u8A0A\u6216 Authy \u96D9\u91CD\u9A57\u8B49",
2022
+ 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",
2023
+ mfaEnrollmentSettingsLink: "\u958B\u555F Coinbase \u5B89\u5168\u8A2D\u5B9A",
2005
2024
  termsNoticePrefix: "\u9EDE\u64CA\u300C\u78BA\u8A8D\u8A02\u55AE\u300D\u5373\u8868\u793A\u60A8\u540C\u610F\u6211\u5011\u7684",
2006
2025
  termsNoticeLink: "\u689D\u6B3E",
2007
2026
  termsNoticeSuffix: "\u3002"
@@ -2132,7 +2151,12 @@ var zh_TW_default = {
2132
2151
  prefix: "\u7E7C\u7E8C\u5373\u8868\u793A\u60A8\u540C\u610F Coinbase \u7684",
2133
2152
  separator: "\u3001",
2134
2153
  lastSeparator: "\u548C",
2135
- suffix: "\u3002Coinbase \u662F\u672C\u6CD5\u5E63\u514C\u52A0\u5BC6\u8CA8\u5E63\u5132\u503C\u670D\u52D9\u7684\u53D7\u76E3\u7BA1\u63D0\u4F9B\u65B9\u3002"
2154
+ suffix: "\u3002",
2155
+ agreements: {
2156
+ guest_checkout_terms: "\u670D\u52D9\u689D\u6B3E",
2157
+ user_agreement: "\u4F7F\u7528\u8005\u5354\u8B70",
2158
+ privacy_policy: "\u96B1\u79C1\u6B0A\u653F\u7B56"
2159
+ }
2136
2160
  },
2137
2161
  errors: {
2138
2162
  rateLimited: "\u5617\u8A66\u6B21\u6578\u904E\u591A\u3002\u8ACB\u7A0D\u7B49\u5E7E\u5206\u9418\u5F8C\u91CD\u8A66\u3002",
@@ -3071,13 +3095,15 @@ import {
3071
3095
  } from "@unifold/core";
3072
3096
 
3073
3097
  // src/hooks/use-deposit-address.ts
3074
- import { useEffect as useEffect3 } from "react";
3075
3098
  import { useQuery } from "@tanstack/react-query";
3076
3099
  import {
3077
3100
  createDepositAddress,
3078
3101
  isDepositAddressValidationError
3079
3102
  } from "@unifold/core";
3080
3103
 
3104
+ // src/hooks/use-internal-user-id.ts
3105
+ import { useEffect as useEffect3 } from "react";
3106
+
3081
3107
  // src/context/AnalyticsContext.tsx
3082
3108
  import * as React5 from "react";
3083
3109
  import { jsx as jsx5 } from "react/jsx-runtime";
@@ -3109,6 +3135,15 @@ function useAnalytics() {
3109
3135
  return { track, startSession, endSession, getSessionId, setUserId, subscribe };
3110
3136
  }
3111
3137
 
3138
+ // src/hooks/use-internal-user-id.ts
3139
+ function useStampInternalUserId({ internalUserId }) {
3140
+ const { setUserId } = useAnalytics();
3141
+ useEffect3(() => {
3142
+ if (!internalUserId) return;
3143
+ setUserId(internalUserId);
3144
+ }, [internalUserId, setUserId]);
3145
+ }
3146
+
3112
3147
  // src/hooks/use-deposit-address.ts
3113
3148
  function useDepositAddress(params) {
3114
3149
  const {
@@ -3122,7 +3157,6 @@ function useDepositAddress(params) {
3122
3157
  contractCalls,
3123
3158
  enabled = true
3124
3159
  } = params;
3125
- const { setUserId } = useAnalytics();
3126
3160
  const query = useQuery({
3127
3161
  queryKey: [
3128
3162
  "unifold",
@@ -3165,11 +3199,7 @@ function useDepositAddress(params) {
3165
3199
  retryDelay: (attempt) => Math.min(1e3 * 2 ** attempt, 1e4)
3166
3200
  // 1s, 2s, 4s (max 10s)
3167
3201
  });
3168
- useEffect3(() => {
3169
- if (query.data?.user_id) {
3170
- setUserId(query.data.user_id);
3171
- }
3172
- }, [query.data?.user_id, setUserId]);
3202
+ useStampInternalUserId({ internalUserId: query.data?.user_id });
3173
3203
  return query;
3174
3204
  }
3175
3205
 
@@ -5475,7 +5505,11 @@ function BuyWithCard({
5475
5505
  method: "card",
5476
5506
  flow_type: "deposit",
5477
5507
  provider: selectedProvider.service_provider,
5478
- amount_usd: amount
5508
+ amount_usd: amount,
5509
+ // The execution only exists once the provider's payout lands on this
5510
+ // wallet, and it carries this id — so it's the join key for a purchase
5511
+ // that starts here and never reaches flow_completed.
5512
+ deposit_wallet_id: depositWalletId
5479
5513
  });
5480
5514
  onEvent?.({
5481
5515
  id: generatePrefixedKSUID2("sevt"),
@@ -6987,6 +7021,8 @@ var BuyWithWalletPay = React8.forwardRef(
6987
7021
  const [limitUpgradeError, setLimitUpgradeError] = useState15(null);
6988
7022
  const [upgradeSubmitting, setUpgradeSubmitting] = useState15(false);
6989
7023
  const upgradeFlowAbortRef = useRef6(null);
7024
+ const applyLimitRoutingRef = useRef6(() => {
7025
+ });
6990
7026
  const [amount, setAmount] = useState15(amountUsd ?? "");
6991
7027
  const [preparedOrder, setPreparedOrder] = useState15(null);
6992
7028
  const sessionAbortRef = useRef6(null);
@@ -7121,6 +7157,14 @@ var BuyWithWalletPay = React8.forwardRef(
7121
7157
  },
7122
7158
  [email, normalizedPhone, userId]
7123
7159
  );
7160
+ const { action: initialAction, refetchWalletPayLimits } = useWalletPayInitialScreen({
7161
+ userId,
7162
+ publishableKey,
7163
+ normalizedPhone,
7164
+ userEmail,
7165
+ sessionPhoneVerified: verificationSession?.phone?.status === "verified",
7166
+ isWaiting: defaultTokenLoading || legalAgreementsLoading || userIpInfoLoading
7167
+ });
7124
7168
  const createSessionAndSendOtp = useCallback4(async () => {
7125
7169
  setView("submitting_session");
7126
7170
  setErrorMessage("");
@@ -7130,7 +7174,12 @@ var BuyWithWalletPay = React8.forwardRef(
7130
7174
  email,
7131
7175
  phone: normalizedPhone,
7132
7176
  terms_accepted: true,
7133
- email_already_verified: true
7177
+ // TODO: when the API honors `false`, this will trigger email OTP.
7178
+ // Today `false` still skips email (we don't verify it) and JWT ev
7179
+ // is null. Don't send `true` — that was a fake attest.
7180
+ email_already_verified: false
7181
+ // Coinbase stays Redis-only + JWT until bank-transfer identity
7182
+ // is live. Do not persist from this path yet.
7134
7183
  },
7135
7184
  publishableKey
7136
7185
  );
@@ -7140,6 +7189,34 @@ var BuyWithWalletPay = React8.forwardRef(
7140
7189
  }
7141
7190
  setClientSecret(session.client_secret);
7142
7191
  storeWalletPaySession();
7192
+ if (session.status === "verified") {
7193
+ const tokenRes = await exchangeOnrampVerificationToken(
7194
+ session.id,
7195
+ session.client_secret,
7196
+ publishableKey
7197
+ );
7198
+ setOnrampToken(tokenRes);
7199
+ storeWalletPaySession({
7200
+ emailVerifiedAt: session.email.verified_at,
7201
+ phoneVerifiedAt: session.phone.verified_at,
7202
+ onrampToken: tokenRes.token,
7203
+ onrampTokenExpiresAtMs: Date.now() + tokenRes.expires_in * 1e3
7204
+ });
7205
+ if (amountUsd) setAmount(amountUsd);
7206
+ try {
7207
+ const result = await refetchWalletPayLimits();
7208
+ if (result.data) {
7209
+ const nextView = getViewForLimitStatus(result.data);
7210
+ if (nextView) {
7211
+ applyLimitRoutingRef.current(nextView, result.data.upgrade_status);
7212
+ return;
7213
+ }
7214
+ }
7215
+ } catch {
7216
+ }
7217
+ setView("amount");
7218
+ return;
7219
+ }
7143
7220
  await sendOnrampVerificationOtp(session.id, "phone", session.client_secret, publishableKey);
7144
7221
  setResendCooldown(30);
7145
7222
  setView("phone_otp");
@@ -7148,19 +7225,19 @@ var BuyWithWalletPay = React8.forwardRef(
7148
7225
  setErrorMessage(prettifyVerificationError(raw));
7149
7226
  setView("phone_input");
7150
7227
  }
7151
- }, [email, normalizedPhone, publishableKey, storeWalletPaySession]);
7228
+ }, [
7229
+ email,
7230
+ normalizedPhone,
7231
+ publishableKey,
7232
+ storeWalletPaySession,
7233
+ userId,
7234
+ amountUsd,
7235
+ refetchWalletPayLimits
7236
+ ]);
7152
7237
  const clearUpgradeFields = useCallback4(() => {
7153
7238
  setSsnLast4("");
7154
7239
  setDobInput("");
7155
7240
  }, []);
7156
- const { action: initialAction, refetchWalletPayLimits } = useWalletPayInitialScreen({
7157
- userId,
7158
- publishableKey,
7159
- normalizedPhone,
7160
- userEmail,
7161
- sessionPhoneVerified: verificationSession?.phone?.status === "verified",
7162
- isWaiting: defaultTokenLoading || legalAgreementsLoading || userIpInfoLoading
7163
- });
7164
7241
  const pollLimitUpgradeStatus = useCallback4(
7165
7242
  async (signal) => {
7166
7243
  const POLL_INTERVAL_MS5 = 1500;
@@ -7271,6 +7348,7 @@ var BuyWithWalletPay = React8.forwardRef(
7271
7348
  },
7272
7349
  [startUpgradePolling]
7273
7350
  );
7351
+ applyLimitRoutingRef.current = applyLimitRouting;
7274
7352
  const hasInitializedRef = useRef6(false);
7275
7353
  useEffect12(() => {
7276
7354
  if (hasInitializedRef.current) return;
@@ -7500,7 +7578,10 @@ var BuyWithWalletPay = React8.forwardRef(
7500
7578
  track("flow_started", {
7501
7579
  method,
7502
7580
  flow_type: "deposit",
7503
- amount_usd: walletPayAmountUsd
7581
+ amount_usd: walletPayAmountUsd,
7582
+ // No execution yet — one appears when the payout lands on this
7583
+ // wallet, carrying the same id.
7584
+ deposit_wallet_id: depositWalletId
7504
7585
  });
7505
7586
  onEvent?.({
7506
7587
  id: generatePrefixedKSUID3("sevt"),
@@ -7550,6 +7631,7 @@ var BuyWithWalletPay = React8.forwardRef(
7550
7631
  defaultTokenLoading,
7551
7632
  defaultTokenError,
7552
7633
  depositWallet?.address,
7634
+ depositWalletId,
7553
7635
  userEmail,
7554
7636
  publishableKey,
7555
7637
  onEvent,
@@ -8524,7 +8606,7 @@ var FALLBACK_AGREEMENTS = [
8524
8606
  {
8525
8607
  key: "guest_checkout_terms",
8526
8608
  name: "Guest Checkout Terms of Service",
8527
- url: "https://www.coinbase.com/legal/cb-pay/onramp-guest-checkout-terms"
8609
+ url: "https://www.coinbase.com/legal/guest-checkout/us"
8528
8610
  },
8529
8611
  {
8530
8612
  key: "user_agreement",
@@ -8544,22 +8626,24 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8544
8626
  return /* @__PURE__ */ jsxs14(
8545
8627
  "div",
8546
8628
  {
8547
- className: "uf-mt-3 uf-mb-3 uf-px-1 uf-space-y-1.5 uf-animate-pulse",
8629
+ className: "uf-mt-3 uf-mb-3 uf-px-1 uf-space-y-1.5 uf-animate-pulse uf-opacity-50",
8548
8630
  "aria-hidden": true,
8549
8631
  "aria-busy": "true",
8550
8632
  children: [
8551
- /* @__PURE__ */ jsx18("div", { className: "uf-h-2.5 uf-rounded uf-w-full", style: { backgroundColor: colors2.card } }),
8552
- /* @__PURE__ */ jsx18("div", { className: "uf-h-2.5 uf-rounded uf-w-11/12", style: { backgroundColor: colors2.card } }),
8553
- /* @__PURE__ */ jsx18("div", { className: "uf-h-2.5 uf-rounded uf-w-3/4", style: { backgroundColor: colors2.card } })
8633
+ /* @__PURE__ */ jsx18("div", { className: "uf-h-2 uf-rounded uf-w-full", style: { backgroundColor: colors2.card } }),
8634
+ /* @__PURE__ */ jsx18("div", { className: "uf-h-2 uf-rounded uf-w-11/12", style: { backgroundColor: colors2.card } }),
8635
+ /* @__PURE__ */ jsx18("div", { className: "uf-h-2 uf-rounded uf-w-3/4", style: { backgroundColor: colors2.card } })
8554
8636
  ]
8555
8637
  }
8556
8638
  );
8557
8639
  }
8558
8640
  const agreements = legalAgreements?.agreements ?? FALLBACK_AGREEMENTS;
8641
+ const shortLabels = t.agreements;
8642
+ const lastSeparator = t.lastSeparator.replace(/\s+$/, "\xA0");
8559
8643
  return /* @__PURE__ */ jsxs14(
8560
8644
  "p",
8561
8645
  {
8562
- className: "uf-text-[11px] uf-leading-relaxed uf-mt-3 uf-mb-3 uf-px-1",
8646
+ className: "uf-text-[10px] uf-leading-relaxed uf-mt-3 uf-mb-3 uf-opacity-60 uf-text-center",
8563
8647
  style: { color: colors2.foregroundMuted, fontFamily: fonts.regular },
8564
8648
  children: [
8565
8649
  t.prefix,
@@ -8570,12 +8654,12 @@ function LegalDisclaimer({ legalAgreements, loading }) {
8570
8654
  href: a.url,
8571
8655
  target: "_blank",
8572
8656
  rel: "noopener noreferrer",
8573
- className: "uf-underline",
8574
- style: { color: colors2.foreground },
8575
- children: a.name
8657
+ className: "uf-no-underline",
8658
+ style: { color: "inherit", textDecoration: "none", whiteSpace: "nowrap" },
8659
+ children: shortLabels[a.key] ?? a.name
8576
8660
  }
8577
8661
  ),
8578
- idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? t.lastSeparator : ""
8662
+ idx < arr.length - 2 ? t.separator : idx === arr.length - 2 ? lastSeparator : ""
8579
8663
  ] }, a.key)),
8580
8664
  t.suffix
8581
8665
  ]
@@ -9231,22 +9315,6 @@ import {
9231
9315
  generatePrefixedKSUID as generatePrefixedKSUID5,
9232
9316
  DepositEventType as DepositEventType4
9233
9317
  } from "@unifold/core";
9234
-
9235
- // src/hooks/use-cashapp-limits.ts
9236
- import { useQuery as useQuery9 } from "@tanstack/react-query";
9237
- import { getCashAppLimits } from "@unifold/core";
9238
- function useCashAppLimits({ publishableKey, currency = "usd" }) {
9239
- return useQuery9({
9240
- queryKey: ["unifold", "cashAppLimits", currency, publishableKey],
9241
- queryFn: () => getCashAppLimits(currency, publishableKey),
9242
- enabled: !!publishableKey,
9243
- staleTime: 2 * 60 * 6e4,
9244
- gcTime: 4 * 60 * 6e4,
9245
- retry: 2
9246
- });
9247
- }
9248
-
9249
- // src/components/deposits/PayWithCashApp.tsx
9250
9318
  import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
9251
9319
  var POLL_INTERVAL_MS3 = 5e3;
9252
9320
  var FALLBACK_MIN_USD = 5;
@@ -9259,6 +9327,7 @@ function PayWithCashApp({
9259
9327
  destinationChainType,
9260
9328
  destinationChainId,
9261
9329
  destinationTokenAddress,
9330
+ contractCalls,
9262
9331
  cashAppIconUrl,
9263
9332
  view: controlledView,
9264
9333
  onViewChange,
@@ -9282,9 +9351,11 @@ function PayWithCashApp({
9282
9351
  const b = parseInt(bg.slice(4, 6), 16);
9283
9352
  return (r * 299 + g * 587 + b * 114) / 1e3 < 128;
9284
9353
  })();
9285
- const { data: limits, isLoading: limitsLoading } = useCashAppLimits({ publishableKey });
9286
- const minUsd = limits?.minimum_amount ?? FALLBACK_MIN_USD;
9287
- const maxUsd = limits?.maximum_amount ?? null;
9354
+ const { data: fiatCurrencies, isLoading: limitsLoading } = useFiatCurrencies({
9355
+ publishableKey
9356
+ });
9357
+ const minUsd = fiatCurrencies?.data.find((entry) => entry.currency_code.toLowerCase() === "usd")?.minimum_amount ?? FALLBACK_MIN_USD;
9358
+ const quickAmounts = SUGGESTED_AMOUNTS.filter((value) => value >= minUsd);
9288
9359
  const [amount, setAmount] = useState18(() => sanitizePrefilledUsd(prefilledAmountUsd));
9289
9360
  const [loading, setLoading] = useState18(false);
9290
9361
  const [session, setSession] = useState18(null);
@@ -9339,6 +9410,8 @@ function PayWithCashApp({
9339
9410
  setError(strings.depositModal.cashAppErrors.missingFields);
9340
9411
  return;
9341
9412
  }
9413
+ const amountNum2 = parseFloat(amount);
9414
+ if (isNaN(amountNum2) || amountNum2 < minUsd) return;
9342
9415
  setLoading(true);
9343
9416
  setError(null);
9344
9417
  try {
@@ -9351,7 +9424,8 @@ function PayWithCashApp({
9351
9424
  destination_token_address: destinationTokenAddress,
9352
9425
  recipient_address: recipientAddress,
9353
9426
  source_amount: amount,
9354
- external_id: externalId
9427
+ external_id: externalId,
9428
+ ...contractCalls?.length ? { contract_calls: contractCalls } : {}
9355
9429
  },
9356
9430
  publishableKey
9357
9431
  );
@@ -9364,7 +9438,10 @@ function PayWithCashApp({
9364
9438
  track("flow_started", {
9365
9439
  method: "cashapp",
9366
9440
  flow_type: "deposit",
9367
- amount_usd: amount
9441
+ amount_usd: amount,
9442
+ // No execution yet — one appears when the payout lands on this
9443
+ // wallet, carrying the same id.
9444
+ deposit_wallet_id: depositWalletId
9368
9445
  });
9369
9446
  onEvent?.({
9370
9447
  id: generatePrefixedKSUID5("sevt"),
@@ -9383,12 +9460,15 @@ function PayWithCashApp({
9383
9460
  }
9384
9461
  }, [
9385
9462
  amount,
9463
+ minUsd,
9386
9464
  userId,
9387
9465
  publishableKey,
9388
9466
  recipientAddress,
9389
9467
  destinationChainType,
9390
9468
  destinationChainId,
9391
9469
  destinationTokenAddress,
9470
+ contractCalls,
9471
+ depositWalletId,
9392
9472
  onEvent,
9393
9473
  onDepositError,
9394
9474
  track,
@@ -9484,8 +9564,7 @@ function PayWithCashApp({
9484
9564
  const amountNum = parseFloat(amount);
9485
9565
  const hasInput = !isNaN(amountNum) && amountNum > 0;
9486
9566
  const belowMin = hasInput && amountNum < minUsd;
9487
- const aboveMax = hasInput && maxUsd !== null && amountNum > maxUsd;
9488
- const isValidAmount = hasInput && !belowMin && !aboveMax;
9567
+ const isValidAmount = hasInput && !belowMin;
9489
9568
  if (view === "amount") {
9490
9569
  return /* @__PURE__ */ jsxs16(
9491
9570
  "div",
@@ -9514,7 +9593,7 @@ function PayWithCashApp({
9514
9593
  }
9515
9594
  }
9516
9595
  ) }) }),
9517
- /* @__PURE__ */ jsx20("div", { className: "uf-flex uf-gap-3 uf-justify-center", children: SUGGESTED_AMOUNTS.map((quickAmount) => {
9596
+ /* @__PURE__ */ jsx20("div", { className: "uf-flex uf-gap-3 uf-justify-center", children: quickAmounts.map((quickAmount) => {
9518
9597
  const isSelected = parseFloat(amount) === quickAmount;
9519
9598
  return /* @__PURE__ */ jsxs16(
9520
9599
  "button",
@@ -9540,23 +9619,17 @@ function PayWithCashApp({
9540
9619
  quickAmount
9541
9620
  );
9542
9621
  }) }),
9543
- amount && amountNum < minUsd && !error ? /* @__PURE__ */ jsx20(
9622
+ !limitsLoading && minUsd > 0 && /* @__PURE__ */ jsx20(
9544
9623
  "div",
9545
9624
  {
9546
- className: "uf-text-sm uf-text-center uf-mt-3 uf-px-2",
9547
- style: { color: colors2.error, fontFamily: fonts.regular },
9548
- children: interpolate(strings.common.minimumAmount, { amount: `$${minUsd}` })
9549
- }
9550
- ) : amount && maxUsd !== null && amountNum > maxUsd && !error ? /* @__PURE__ */ jsx20(
9551
- "div",
9552
- {
9553
- className: "uf-text-sm uf-text-center uf-mt-3 uf-px-2",
9554
- style: { color: colors2.error, fontFamily: fonts.regular },
9555
- children: interpolate(strings.common.maximumAmount, {
9556
- amount: `$${maxUsd.toLocaleString()}`
9625
+ className: "uf-text-center uf-text-xs uf-mt-4",
9626
+ style: { color: colors2.warning, fontFamily: fonts.regular },
9627
+ children: interpolate(strings.common.minimumDeposit, {
9628
+ amount: `$${minUsd.toFixed(2)}`
9557
9629
  })
9558
9630
  }
9559
- ) : error ? /* @__PURE__ */ jsx20(
9631
+ ),
9632
+ error ? /* @__PURE__ */ jsx20(
9560
9633
  "div",
9561
9634
  {
9562
9635
  className: "uf-text-sm uf-text-center uf-mt-3 uf-px-2",
@@ -9565,7 +9638,7 @@ function PayWithCashApp({
9565
9638
  }
9566
9639
  ) : null
9567
9640
  ] }),
9568
- /* @__PURE__ */ jsx20("div", { className: "uf-px-1", children: /* @__PURE__ */ jsx20(
9641
+ /* @__PURE__ */ jsx20("div", { className: "uf-px-1 uf-mt-5", children: /* @__PURE__ */ jsx20(
9569
9642
  "button",
9570
9643
  {
9571
9644
  onClick: handleCreateSession,
@@ -9794,7 +9867,7 @@ import {
9794
9867
  } from "@unifold/core";
9795
9868
 
9796
9869
  // src/hooks/use-bank-transfer-providers.ts
9797
- import { useQuery as useQuery10 } from "@tanstack/react-query";
9870
+ import { useQuery as useQuery9 } from "@tanstack/react-query";
9798
9871
  import { getBankTransferProviders } from "@unifold/core";
9799
9872
  function useBankTransferProviders({
9800
9873
  publishableKey,
@@ -9802,7 +9875,7 @@ function useBankTransferProviders({
9802
9875
  countryCode
9803
9876
  }) {
9804
9877
  const normalizedCountry = countryCode?.toUpperCase();
9805
- const { data: providers, isLoading } = useQuery10({
9878
+ const { data: providers, isLoading } = useQuery9({
9806
9879
  queryKey: ["unifold", "bankTransferProviders", publishableKey, normalizedCountry ?? null],
9807
9880
  queryFn: () => getBankTransferProviders(publishableKey, { countryCode: normalizedCountry }),
9808
9881
  enabled,
@@ -10009,7 +10082,10 @@ function BankTransfer({
10009
10082
  method: "bank_transfer",
10010
10083
  flow_type: "deposit",
10011
10084
  provider: request.service_provider,
10012
- amount_usd: request.source_amount
10085
+ amount_usd: request.source_amount,
10086
+ // No execution yet — one appears when the provider's payout lands on
10087
+ // this wallet, carrying the same id.
10088
+ deposit_wallet_id: pollingWalletId
10013
10089
  });
10014
10090
  onEvent?.({
10015
10091
  id: generatePrefixedKSUID6("sevt"),
@@ -16274,7 +16350,11 @@ function PayWithStripeLink({
16274
16350
  if (checkoutResult.successful) {
16275
16351
  handledTerminalSessionRef.current = null;
16276
16352
  executionReconciledSessionRef.current = null;
16277
- track("flow_started", { method: "stripe_link", flow_type: "deposit" });
16353
+ track("flow_started", {
16354
+ method: "stripe_link",
16355
+ flow_type: "deposit",
16356
+ deposit_wallet_id: depositWalletId
16357
+ });
16278
16358
  setConfirmedSessionId(activeSessionId);
16279
16359
  setSessionStatus(STRIPE_SESSION_STATUS.FULFILLMENT_PROCESSING);
16280
16360
  if (selectedTokenSingleUseRef.current) clearSelectedPaymentToken();
@@ -17771,7 +17851,14 @@ function PayWithStripeLink({
17771
17851
  ]
17772
17852
  }
17773
17853
  ),
17774
- /* @__PURE__ */ jsx50("div", { ref: authMountRef, className: "uf-w-full uf-min-h-[200px]" })
17854
+ /* @__PURE__ */ jsx50(
17855
+ "div",
17856
+ {
17857
+ ref: authMountRef,
17858
+ className: "uf-w-full uf-min-h-[200px]",
17859
+ style: { colorScheme: "only light" }
17860
+ }
17861
+ )
17775
17862
  ]
17776
17863
  }
17777
17864
  );
@@ -18351,7 +18438,7 @@ import {
18351
18438
  } from "@unifold/core";
18352
18439
 
18353
18440
  // src/hooks/use-project-config.ts
18354
- import { useQuery as useQuery11, keepPreviousData } from "@tanstack/react-query";
18441
+ import { useQuery as useQuery10, keepPreviousData } from "@tanstack/react-query";
18355
18442
  import { getProjectConfig } from "@unifold/core";
18356
18443
  function useProjectConfig({
18357
18444
  publishableKey,
@@ -18363,7 +18450,7 @@ function useProjectConfig({
18363
18450
  data: projectConfig,
18364
18451
  isLoading,
18365
18452
  error
18366
- } = useQuery11({
18453
+ } = useQuery10({
18367
18454
  // Country is part of the key so a region change refetches the region-aware
18368
18455
  // config. Omitted when undefined so callers that don't pass a country keep
18369
18456
  // sharing the base cache entry.
@@ -18382,7 +18469,7 @@ function useProjectConfig({
18382
18469
  }
18383
18470
 
18384
18471
  // src/hooks/use-integration-transfer-default-token.ts
18385
- import { useQuery as useQuery12 } from "@tanstack/react-query";
18472
+ import { useQuery as useQuery11 } from "@tanstack/react-query";
18386
18473
  import {
18387
18474
  getIntegrationTransferDefaultToken
18388
18475
  } from "@unifold/core";
@@ -18391,7 +18478,7 @@ function useIntegrationTransferDefaultToken({
18391
18478
  publishableKey,
18392
18479
  enabled = true
18393
18480
  }) {
18394
- return useQuery12({
18481
+ return useQuery11({
18395
18482
  queryKey: [
18396
18483
  "unifold",
18397
18484
  "integrationTransferDefaultToken",
@@ -19079,6 +19166,9 @@ function CoinbaseConnect({
19079
19166
  flow_type: "deposit",
19080
19167
  method: "exchange_connect",
19081
19168
  provider: IntegrationProvider.COINBASE,
19169
+ // No execution yet — one appears when the exchange's transfer lands on
19170
+ // this wallet, carrying the same id.
19171
+ deposit_wallet_id: transferDepositWalletId,
19082
19172
  amount_usd: sendAmount || void 0,
19083
19173
  currency: selectedAsset?.currency.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
19084
19174
  chain: sourceChain?.chain_id,
@@ -19086,6 +19176,34 @@ function CoinbaseConnect({
19086
19176
  network: sourceChain?.network
19087
19177
  });
19088
19178
  };
19179
+ const applyConfirmResult = (result) => {
19180
+ if (result.status === "mfa_required") {
19181
+ track("verification_started", {
19182
+ flow_type: "deposit",
19183
+ method: "exchange_connect",
19184
+ provider: IntegrationProvider.COINBASE,
19185
+ verification_type: "mfa",
19186
+ verification_status: result.status
19187
+ });
19188
+ transitionTo("mfa");
19189
+ setMfaCode("");
19190
+ setMfaError(false);
19191
+ } else if (result.status === "mfa_enrollment_required") {
19192
+ transitionTo("mfa_enrollment");
19193
+ } else if (result.status === "completed") {
19194
+ trackTransferStarted();
19195
+ setConfirmResult(result);
19196
+ transitionTo("success");
19197
+ onTransferSuccess?.(result);
19198
+ } else {
19199
+ setErrorMessage(result.message || t.errors.transferFailed);
19200
+ transitionTo("error");
19201
+ onTransferError?.({
19202
+ message: result.message || t.errors.transferFailed,
19203
+ code: result.status
19204
+ });
19205
+ }
19206
+ };
19089
19207
  const handleConfirmTransfer = async () => {
19090
19208
  if (!transferIntent || !accessToken) return;
19091
19209
  setIsLoading(true);
@@ -19120,30 +19238,7 @@ function CoinbaseConnect({
19120
19238
  });
19121
19239
  throw err;
19122
19240
  });
19123
- if (result.status === "mfa_required") {
19124
- track("verification_started", {
19125
- flow_type: "deposit",
19126
- method: "exchange_connect",
19127
- provider: IntegrationProvider.COINBASE,
19128
- verification_type: "mfa",
19129
- verification_status: result.status
19130
- });
19131
- transitionTo("mfa");
19132
- setMfaCode("");
19133
- setMfaError(false);
19134
- } else if (result.status === "completed") {
19135
- trackTransferStarted();
19136
- setConfirmResult(result);
19137
- transitionTo("success");
19138
- onTransferSuccess?.(result);
19139
- } else {
19140
- setErrorMessage(result.message || t.errors.transferFailed);
19141
- transitionTo("error");
19142
- onTransferError?.({
19143
- message: result.message || t.errors.transferFailed,
19144
- code: result.status
19145
- });
19146
- }
19241
+ applyConfirmResult(result);
19147
19242
  } catch (err) {
19148
19243
  const message = err instanceof Error ? err.message : t.errors.transferFailed;
19149
19244
  setErrorMessage(message);
@@ -19300,6 +19395,9 @@ function CoinbaseConnect({
19300
19395
  setShowTransferDetails(false);
19301
19396
  transitionTo("enter_amount");
19302
19397
  break;
19398
+ case "mfa_enrollment":
19399
+ transitionTo("transfer_confirm");
19400
+ break;
19303
19401
  case "mfa":
19304
19402
  transitionTo("transfer_confirm");
19305
19403
  break;
@@ -20856,6 +20954,55 @@ function CoinbaseConnect({
20856
20954
  ] })
20857
20955
  ] });
20858
20956
  }
20957
+ if (view === "mfa_enrollment") {
20958
+ return /* @__PURE__ */ jsxs46(Fragment9, { children: [
20959
+ /* @__PURE__ */ jsx51(DepositHeader, { title: t.title, showBack: true, onBack: handleBack, onClose }),
20960
+ /* @__PURE__ */ jsxs46(
20961
+ "div",
20962
+ {
20963
+ className: "uf-flex uf-flex-col uf-items-center uf-px-4 uf-pb-4",
20964
+ style: viewTransitionStyle,
20965
+ children: [
20966
+ /* @__PURE__ */ jsx51(
20967
+ DepositErrorState,
20968
+ {
20969
+ title: t.transfer.mfaEnrollmentTitle,
20970
+ message: t.transfer.mfaEnrollmentMessage
20971
+ }
20972
+ ),
20973
+ /* @__PURE__ */ jsxs46(
20974
+ "a",
20975
+ {
20976
+ href: "https://www.coinbase.com/settings/security",
20977
+ target: "_blank",
20978
+ rel: "noopener noreferrer",
20979
+ className: "uf-text-sm uf-underline uf-mb-8 uf-inline-flex uf-items-center uf-gap-1",
20980
+ style: { color: colors2.primary, fontFamily: fonts.medium },
20981
+ children: [
20982
+ t.transfer.mfaEnrollmentSettingsLink,
20983
+ /* @__PURE__ */ jsx51(ExternalLink4, { className: "uf-w-3.5 uf-h-3.5" })
20984
+ ]
20985
+ }
20986
+ ),
20987
+ /* @__PURE__ */ jsx51(
20988
+ "button",
20989
+ {
20990
+ onClick: handleConfirmTransfer,
20991
+ disabled: isLoading,
20992
+ 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",
20993
+ style: {
20994
+ backgroundColor: colors2.primary,
20995
+ color: "#fff",
20996
+ fontFamily: fonts.medium
20997
+ },
20998
+ children: isLoading ? /* @__PURE__ */ jsx51(Loader27, { className: "uf-w-4 uf-h-4 uf-animate-spin" }) : t.continue
20999
+ }
21000
+ )
21001
+ ]
21002
+ }
21003
+ )
21004
+ ] });
21005
+ }
20859
21006
  if (view === "mfa") {
20860
21007
  return /* @__PURE__ */ jsxs46(Fragment9, { children: [
20861
21008
  /* @__PURE__ */ jsx51(DepositHeader, { title: t.title, showBack: true, onBack: handleBack, onClose }),
@@ -21141,13 +21288,13 @@ function resolveDepositMenuLayout(layout, displayMode) {
21141
21288
  }
21142
21289
 
21143
21290
  // src/hooks/use-exchanges.ts
21144
- import { useQuery as useQuery13 } from "@tanstack/react-query";
21291
+ import { useQuery as useQuery12 } from "@tanstack/react-query";
21145
21292
  import { getExchanges } from "@unifold/core";
21146
21293
  function useExchanges({
21147
21294
  publishableKey,
21148
21295
  enabled = true
21149
21296
  }) {
21150
- const { data: exchanges = [], isLoading } = useQuery13({
21297
+ const { data: exchanges = [], isLoading } = useQuery12({
21151
21298
  queryKey: ["unifold", "exchanges", publishableKey],
21152
21299
  queryFn: () => getExchanges(void 0, publishableKey).then((res) => res.data),
21153
21300
  enabled,
@@ -21159,7 +21306,7 @@ function useExchanges({
21159
21306
  }
21160
21307
 
21161
21308
  // src/hooks/use-public-incident.ts
21162
- import { useQuery as useQuery14 } from "@tanstack/react-query";
21309
+ import { useQuery as useQuery13 } from "@tanstack/react-query";
21163
21310
  import { getPublicIncident } from "@unifold/core";
21164
21311
  function usePublicIncident({
21165
21312
  publishableKey,
@@ -21169,7 +21316,7 @@ function usePublicIncident({
21169
21316
  data: incident,
21170
21317
  isLoading,
21171
21318
  error
21172
- } = useQuery14({
21319
+ } = useQuery13({
21173
21320
  queryKey: ["unifold", "publicIncident", publishableKey],
21174
21321
  queryFn: () => getPublicIncident(publishableKey),
21175
21322
  enabled,
@@ -21181,7 +21328,7 @@ function usePublicIncident({
21181
21328
  }
21182
21329
 
21183
21330
  // src/hooks/use-wallet-pay-providers.ts
21184
- import { useQuery as useQuery15 } from "@tanstack/react-query";
21331
+ import { useQuery as useQuery14 } from "@tanstack/react-query";
21185
21332
  import {
21186
21333
  getWalletPayProviders
21187
21334
  } from "@unifold/core";
@@ -21190,7 +21337,7 @@ function useWalletPayProviders({
21190
21337
  publishableKey,
21191
21338
  enabled = true
21192
21339
  }) {
21193
- const { data: providers, isLoading } = useQuery15({
21340
+ const { data: providers, isLoading } = useQuery14({
21194
21341
  queryKey: ["unifold", "walletPayProviders", method, publishableKey],
21195
21342
  queryFn: () => getWalletPayProviders(method, publishableKey),
21196
21343
  enabled,
@@ -21483,7 +21630,7 @@ function useAllowedCountry(publishableKey) {
21483
21630
  }
21484
21631
 
21485
21632
  // src/hooks/use-address-validation.ts
21486
- import { useQuery as useQuery16 } from "@tanstack/react-query";
21633
+ import { useQuery as useQuery15 } from "@tanstack/react-query";
21487
21634
  import {
21488
21635
  verifyRecipientAddress
21489
21636
  } from "@unifold/core";
@@ -21497,7 +21644,7 @@ function useAddressValidation({
21497
21644
  refetchOnMount = false
21498
21645
  }) {
21499
21646
  const shouldValidate = enabled && !!recipientAddress && !!destinationChainType && !!destinationChainId && !!destinationTokenAddress;
21500
- const { data, isLoading, error } = useQuery16({
21647
+ const { data, isLoading, error } = useQuery15({
21501
21648
  queryKey: [
21502
21649
  "unifold",
21503
21650
  "addressValidation",
@@ -22960,7 +23107,7 @@ import {
22960
23107
  } from "@unifold/core";
22961
23108
 
22962
23109
  // src/hooks/use-hypercore-activation.ts
22963
- import { useQuery as useQuery17 } from "@tanstack/react-query";
23110
+ import { useQuery as useQuery16 } from "@tanstack/react-query";
22964
23111
  import { checkHypercoreActivation } from "@unifold/core";
22965
23112
 
22966
23113
  // src/lib/constants.ts
@@ -22979,7 +23126,7 @@ function useHypercoreActivation(params) {
22979
23126
  const recipient = recipientAddress?.trim() ?? "";
22980
23127
  const source = sourceAddress?.trim() ?? "";
22981
23128
  const hasAddresses = !!recipient && !!source;
22982
- const { data, isLoading } = useQuery17({
23129
+ const { data, isLoading } = useQuery16({
22983
23130
  queryKey: ["unifold", "hypercoreActivation", source, recipient, publishableKey],
22984
23131
  queryFn: () => checkHypercoreActivation(
22985
23132
  {
@@ -24793,7 +24940,7 @@ async function sendHypercoreEvmTransfer(params) {
24793
24940
  }
24794
24941
 
24795
24942
  // src/hooks/use-deposit-quote.ts
24796
- import { useQuery as useQuery18 } from "@tanstack/react-query";
24943
+ import { useQuery as useQuery17 } from "@tanstack/react-query";
24797
24944
  import { getDepositQuote } from "@unifold/core";
24798
24945
  function useDepositQuote(params) {
24799
24946
  const {
@@ -24820,7 +24967,7 @@ function useDepositQuote(params) {
24820
24967
  ...adjustForSlippage ? { adjust_for_slippage: true } : {},
24821
24968
  ...stablecoinParity ? { stablecoin_parity: true } : {}
24822
24969
  };
24823
- return useQuery18({
24970
+ return useQuery17({
24824
24971
  queryKey: [
24825
24972
  "unifold",
24826
24973
  "depositQuote",
@@ -24848,13 +24995,13 @@ function useDepositQuote(params) {
24848
24995
  }
24849
24996
 
24850
24997
  // src/hooks/use-external-wallets.ts
24851
- import { useQuery as useQuery19 } from "@tanstack/react-query";
24998
+ import { useQuery as useQuery18 } from "@tanstack/react-query";
24852
24999
  import { getExternalWallets } from "@unifold/core";
24853
25000
  function useExternalWallets({
24854
25001
  publishableKey,
24855
25002
  enabled = true
24856
25003
  }) {
24857
- const { data: wallets = [], isLoading } = useQuery19({
25004
+ const { data: wallets = [], isLoading } = useQuery18({
24858
25005
  queryKey: ["unifold", "external-wallets", publishableKey],
24859
25006
  queryFn: () => getExternalWallets(publishableKey).then((res) => res.data),
24860
25007
  enabled: enabled && !!publishableKey,
@@ -27015,7 +27162,11 @@ function WalletConnect({
27015
27162
  chain: String(token.chain_id),
27016
27163
  network: token.network,
27017
27164
  token: token.symbol ?? token.token_address,
27018
- currency: token.currency
27165
+ currency: token.currency,
27166
+ // No execution exists yet — it's created when this send lands on the
27167
+ // wallet. The wallet id is what that execution will carry, so it's
27168
+ // how a send that never completes is matched to what followed.
27169
+ deposit_wallet_id: activeDepositWallet?.id
27019
27170
  });
27020
27171
  setReceivedUsdAtSubmission(checkoutReceivedUsd ?? "0");
27021
27172
  setHasSignedTransaction(true);
@@ -27605,7 +27756,7 @@ function DepositModal({
27605
27756
  const applePaySubTitle = applePaySubTitleProp ?? t.applePayMenu.subtitle;
27606
27757
  const googlePayTitle = googlePayTitleProp ?? t.googlePayMenu.title;
27607
27758
  const googlePaySubTitle = googlePaySubTitleProp ?? t.googlePayMenu.subtitle;
27608
- const { track, startSession, endSession, getSessionId, subscribe } = useAnalytics();
27759
+ const { track, startSession, endSession, getSessionId, setUserId, subscribe } = useAnalytics();
27609
27760
  const normalizedPrefilledAmountUsd = useMemo17(
27610
27761
  () => normalizePrefilledUsdAmount(prefilledAmountUsd),
27611
27762
  [prefilledAmountUsd]
@@ -27613,6 +27764,7 @@ function DepositModal({
27613
27764
  const analyticsMethod = (method) => method === "transfer" ? "transfer_crypto" : method;
27614
27765
  const journeyActiveRef = useRef16(false);
27615
27766
  const journeySessionIdRef = useRef16(void 0);
27767
+ const journeyExternalUserIdRef = useRef16(void 0);
27616
27768
  const externalUserId = userId || void 0;
27617
27769
  const emitEvent = useCallback14(
27618
27770
  (event) => onEvent?.({ ...event, externalUserId }),
@@ -27640,6 +27792,9 @@ function DepositModal({
27640
27792
  method: analyticsMethod(method),
27641
27793
  flow_type: "deposit",
27642
27794
  amount_usd: execution?.destinationAmountUsd ?? execution?.sourceAmountUsd,
27795
+ // The one high-cardinality field we do send: the join key from a
27796
+ // funnel session back to the execution row it produced.
27797
+ execution_id: data.executionId ?? execution?.id,
27643
27798
  // Flat, low-cardinality execution dimensions for PostHog breakdowns
27644
27799
  // (precise/opaque fields like base units, addresses, tx hashes live in
27645
27800
  // the onEvent DirectExecution instead).
@@ -27667,6 +27822,7 @@ function DepositModal({
27667
27822
  method: analyticsMethod(method),
27668
27823
  flow_type: "deposit",
27669
27824
  error_code: error.code,
27825
+ execution_id: execution?.id,
27670
27826
  failure_reason: execution?.failureReason ?? void 0,
27671
27827
  status: execution?.status,
27672
27828
  source_chain_type: execution?.sourceChainType,
@@ -28095,8 +28251,10 @@ function DepositModal({
28095
28251
  const beginJourney = useCallback14(() => {
28096
28252
  journeySessionIdRef.current = startSession();
28097
28253
  journeyActiveRef.current = true;
28254
+ journeyExternalUserIdRef.current = userId;
28255
+ setUserId(null);
28098
28256
  track("widget_opened", { flow_type: "deposit" });
28099
- }, [startSession, track]);
28257
+ }, [startSession, track, setUserId, userId]);
28100
28258
  const handleClose = () => {
28101
28259
  finalizeJourney();
28102
28260
  onOpenChange(false);
@@ -28121,6 +28279,11 @@ function DepositModal({
28121
28279
  }
28122
28280
  beginJourney();
28123
28281
  }, [open]);
28282
+ useLayoutEffect2(() => {
28283
+ if (!open || journeyExternalUserIdRef.current === userId) return;
28284
+ journeyExternalUserIdRef.current = userId;
28285
+ setUserId(null);
28286
+ }, [open, userId, setUserId]);
28124
28287
  useLayoutEffect2(() => {
28125
28288
  if (!open) return;
28126
28289
  if (resetViewTimeoutRef.current) {
@@ -29032,6 +29195,7 @@ function DepositModal({
29032
29195
  destinationChainType,
29033
29196
  destinationChainId,
29034
29197
  destinationTokenAddress,
29198
+ contractCalls,
29035
29199
  cashAppIconUrl: projectConfig?.asset_cdn_url ? `${projectConfig.asset_cdn_url}/api/public/icons/onramps/svg/cashapp.svg` : void 0,
29036
29200
  view: cashAppView,
29037
29201
  onViewChange: setCashAppView,
@@ -29216,12 +29380,12 @@ function useForwardCheckoutEvents(onEvent, journeySessionId) {
29216
29380
  }
29217
29381
 
29218
29382
  // src/hooks/use-payment-intent.ts
29219
- import { useQuery as useQuery20 } from "@tanstack/react-query";
29383
+ import { useQuery as useQuery19 } from "@tanstack/react-query";
29220
29384
  import { retrievePaymentIntent } from "@unifold/core";
29221
29385
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "expired", "refunded", "canceled"]);
29222
29386
  function usePaymentIntent(params) {
29223
29387
  const { clientSecret, publishableKey, enabled = true, pollingInterval = 3e3 } = params;
29224
- return useQuery20({
29388
+ return useQuery19({
29225
29389
  queryKey: ["unifold", "paymentIntent", clientSecret, publishableKey],
29226
29390
  queryFn: () => retrievePaymentIntent(clientSecret, publishableKey),
29227
29391
  enabled: enabled && !!clientSecret && !!publishableKey,
@@ -29395,6 +29559,9 @@ function CheckoutModal({
29395
29559
  flowCompletedRef.current = true;
29396
29560
  track("flow_completed", {
29397
29561
  flow_type: "checkout",
29562
+ // Checkout has no direct execution; the intent is what a funnel
29563
+ // session joins back to.
29564
+ payment_intent_id: data.paymentIntentId,
29398
29565
  ...method ? { method: method === "transfer" ? "transfer_crypto" : method } : {}
29399
29566
  });
29400
29567
  }
@@ -29615,7 +29782,7 @@ function CheckoutModal({
29615
29782
  journeySessionIdRef.current = startSession();
29616
29783
  journeyActiveRef.current = true;
29617
29784
  flowCompletedRef.current = false;
29618
- setUserId(null);
29785
+ setUserId(paymentIntentUserId ?? null);
29619
29786
  track("widget_opened", { flow_type: "checkout" });
29620
29787
  if (resetViewTimeoutRef.current) {
29621
29788
  clearTimeout(resetViewTimeoutRef.current);
@@ -30056,16 +30223,16 @@ function CheckoutModal({
30056
30223
  }
30057
30224
 
30058
30225
  // src/components/withdrawals/WithdrawModal.tsx
30059
- import { useState as useState49, useEffect as useEffect48, useLayoutEffect as useLayoutEffect4, useCallback as useCallback17, useMemo as useMemo21, useRef as useRef20 } from "react";
30226
+ import { useState as useState49, useEffect as useEffect47, useLayoutEffect as useLayoutEffect4, useCallback as useCallback17, useMemo as useMemo21, useRef as useRef20 } from "react";
30060
30227
  import { AlertTriangle as AlertTriangle8, ChevronRight as ChevronRight22, Clock as Clock6 } from "lucide-react";
30061
30228
 
30062
30229
  // src/hooks/use-supported-destination-tokens.ts
30063
- import { useQuery as useQuery21 } from "@tanstack/react-query";
30230
+ import { useQuery as useQuery20 } from "@tanstack/react-query";
30064
30231
  import {
30065
30232
  getSupportedDestinationTokens
30066
30233
  } from "@unifold/core";
30067
30234
  function useSupportedDestinationTokens(publishableKey, enabled = true) {
30068
- return useQuery21({
30235
+ return useQuery20({
30069
30236
  queryKey: ["unifold", "supportedDestinationTokens", publishableKey],
30070
30237
  queryFn: () => getSupportedDestinationTokens(publishableKey),
30071
30238
  staleTime: 1e3 * 60 * 5,
@@ -30097,7 +30264,7 @@ function useDefaultDestinationToken({
30097
30264
  }
30098
30265
 
30099
30266
  // src/hooks/use-source-token-validation.ts
30100
- import { useQuery as useQuery22 } from "@tanstack/react-query";
30267
+ import { useQuery as useQuery21 } from "@tanstack/react-query";
30101
30268
  import { getSupportedDepositTokens as getSupportedDepositTokens3 } from "@unifold/core";
30102
30269
  function useSourceTokenValidation(params) {
30103
30270
  const {
@@ -30109,7 +30276,7 @@ function useSourceTokenValidation(params) {
30109
30276
  enabled = true
30110
30277
  } = params;
30111
30278
  const hasParams = !!sourceChainType && !!sourceChainId && !!sourceTokenAddress;
30112
- return useQuery22({
30279
+ return useQuery21({
30113
30280
  queryKey: [
30114
30281
  "unifold",
30115
30282
  "sourceTokenValidation",
@@ -30159,12 +30326,12 @@ function useSourceTokenValidation(params) {
30159
30326
  }
30160
30327
 
30161
30328
  // src/hooks/use-address-balance.ts
30162
- import { useQuery as useQuery23 } from "@tanstack/react-query";
30329
+ import { useQuery as useQuery22 } from "@tanstack/react-query";
30163
30330
  import { getAddressBalance as getAddressBalance2 } from "@unifold/core";
30164
30331
  function useAddressBalance(params) {
30165
30332
  const { address, chainType, chainId, tokenAddress, publishableKey, enabled = true } = params;
30166
30333
  const hasParams = !!address && !!chainType && !!chainId && !!tokenAddress;
30167
- return useQuery23({
30334
+ return useQuery22({
30168
30335
  queryKey: [
30169
30336
  "unifold",
30170
30337
  "addressBalance",
@@ -30220,11 +30387,11 @@ function useAddressBalance(params) {
30220
30387
  }
30221
30388
 
30222
30389
  // src/hooks/use-executions.ts
30223
- import { useQuery as useQuery24 } from "@tanstack/react-query";
30390
+ import { useQuery as useQuery23 } from "@tanstack/react-query";
30224
30391
  import { queryExecutions as queryExecutions4, ActionType as ActionType4 } from "@unifold/core";
30225
30392
  function useExecutions(userId, publishableKey, options) {
30226
30393
  const actionType = options?.actionType ?? ActionType4.Deposit;
30227
- return useQuery24({
30394
+ const query = useQuery23({
30228
30395
  queryKey: ["unifold", "executions", actionType, userId, publishableKey],
30229
30396
  queryFn: () => queryExecutions4(userId, publishableKey, actionType),
30230
30397
  enabled: (options?.enabled ?? true) && !!userId,
@@ -30233,6 +30400,8 @@ function useExecutions(userId, publishableKey, options) {
30233
30400
  gcTime: 1e3 * 60 * 5,
30234
30401
  refetchOnWindowFocus: false
30235
30402
  });
30403
+ useStampInternalUserId({ internalUserId: query.data?.data[0]?.user_id });
30404
+ return query;
30236
30405
  }
30237
30406
 
30238
30407
  // src/hooks/use-withdraw-polling.ts
@@ -30574,7 +30743,7 @@ function WithdrawDoubleInput({
30574
30743
  }
30575
30744
 
30576
30745
  // src/components/withdrawals/WithdrawForm.tsx
30577
- import { useState as useState47, useCallback as useCallback16, useMemo as useMemo20, useEffect as useEffect46 } from "react";
30746
+ import { useState as useState47, useCallback as useCallback16, useMemo as useMemo20, useEffect as useEffect45 } from "react";
30578
30747
  import {
30579
30748
  AlertTriangle as AlertTriangle7,
30580
30749
  ArrowUpDown,
@@ -30592,7 +30761,7 @@ import {
30592
30761
  } from "@unifold/core";
30593
30762
 
30594
30763
  // src/hooks/use-verify-recipient-address.ts
30595
- import { useQuery as useQuery25 } from "@tanstack/react-query";
30764
+ import { useQuery as useQuery24 } from "@tanstack/react-query";
30596
30765
  import { verifyRecipientAddress as verifyRecipientAddress2 } from "@unifold/core";
30597
30766
  function useVerifyRecipientAddress(params) {
30598
30767
  const {
@@ -30605,7 +30774,7 @@ function useVerifyRecipientAddress(params) {
30605
30774
  } = params;
30606
30775
  const trimmedAddress = recipientAddress?.trim() || "";
30607
30776
  const hasAllParams = !!chainType && !!chainId && !!tokenAddress && trimmedAddress.length > 0;
30608
- return useQuery25({
30777
+ return useQuery24({
30609
30778
  queryKey: [
30610
30779
  "unifold",
30611
30780
  "verifyRecipientAddress",
@@ -30857,8 +31026,7 @@ import { useMemo as useMemo19 } from "react";
30857
31026
  import { ActionType as ActionType6 } from "@unifold/core";
30858
31027
 
30859
31028
  // src/hooks/use-get-deposit-address.ts
30860
- import { useEffect as useEffect45 } from "react";
30861
- import { useQuery as useQuery26 } from "@tanstack/react-query";
31029
+ import { useQuery as useQuery25 } from "@tanstack/react-query";
30862
31030
  import { getDepositAddress } from "@unifold/core";
30863
31031
  function useGetDepositAddress(params) {
30864
31032
  const {
@@ -30871,9 +31039,8 @@ function useGetDepositAddress(params) {
30871
31039
  actionType,
30872
31040
  enabled = true
30873
31041
  } = params;
30874
- const { setUserId } = useAnalytics();
30875
31042
  const canFire = !!userId && !!recipientAddress && !!destinationChainType && !!destinationChainId && !!destinationTokenAddress;
30876
- const query = useQuery26({
31043
+ const query = useQuery25({
30877
31044
  queryKey: [
30878
31045
  "unifold",
30879
31046
  "getDepositAddress",
@@ -30903,11 +31070,7 @@ function useGetDepositAddress(params) {
30903
31070
  refetchOnWindowFocus: false,
30904
31071
  retry: 1
30905
31072
  });
30906
- useEffect45(() => {
30907
- if (query.data?.user_id) {
30908
- setUserId(query.data.user_id);
30909
- }
30910
- }, [query.data?.user_id, setUserId]);
31073
+ useStampInternalUserId({ internalUserId: query.data?.user_id });
30911
31074
  return query;
30912
31075
  }
30913
31076
 
@@ -31047,14 +31210,14 @@ function WithdrawForm({
31047
31210
  const [detailsExpanded, setDetailsExpanded] = useState47(false);
31048
31211
  const [glossaryOpen, setGlossaryOpen] = useState47(false);
31049
31212
  const [isMaxed, setIsMaxed] = useState47(false);
31050
- useEffect46(() => {
31213
+ useEffect45(() => {
31051
31214
  setRecipientAddress(recipientAddressProp || "");
31052
31215
  setAmount("");
31053
31216
  setInputUnit("fiat");
31054
31217
  setSubmitError(null);
31055
31218
  setIsMaxed(false);
31056
31219
  }, [recipientAddressProp]);
31057
- useEffect46(() => {
31220
+ useEffect45(() => {
31058
31221
  setIsMaxed(false);
31059
31222
  }, [balanceData?.balanceBaseUnit]);
31060
31223
  const trimmedAddress = recipientAddress.trim();
@@ -31855,7 +32018,7 @@ function WithdrawExecutionItem({ execution, onClick }) {
31855
32018
  }
31856
32019
 
31857
32020
  // src/components/withdrawals/WithdrawConfirmingView.tsx
31858
- import { useState as useState48, useEffect as useEffect47 } from "react";
32021
+ import { useState as useState48, useEffect as useEffect46 } from "react";
31859
32022
  import { Fragment as Fragment19, jsx as jsx76, jsxs as jsxs66 } from "react/jsx-runtime";
31860
32023
  function truncateAddress4(addr) {
31861
32024
  if (addr.length <= 12) return addr;
@@ -31873,7 +32036,7 @@ function WithdrawConfirmingView({
31873
32036
  const t = strings.withdrawModal.details;
31874
32037
  const [showButton, setShowButton] = useState48(false);
31875
32038
  const latestExecution = executions.length > 0 ? executions[executions.length - 1] : null;
31876
- useEffect47(() => {
32039
+ useEffect46(() => {
31877
32040
  if (latestExecution) return;
31878
32041
  const timer = setTimeout(() => setShowButton(true), SHOW_BUTTON_DELAY_MS);
31879
32042
  return () => clearTimeout(timer);
@@ -32035,7 +32198,7 @@ function WithdrawModal({
32035
32198
  );
32036
32199
  const onEventRef = useRef20(emitEvent);
32037
32200
  onEventRef.current = emitEvent;
32038
- useEffect48(
32201
+ useEffect47(
32039
32202
  () => subscribe((event) => {
32040
32203
  if (event.properties?.flow_type !== "withdraw") return;
32041
32204
  const selection = selectionPayload(event);
@@ -32052,12 +32215,16 @@ function WithdrawModal({
32052
32215
  );
32053
32216
  const journeyActiveRef = useRef20(false);
32054
32217
  const journeySessionIdRef = useRef20(void 0);
32218
+ const journeyExternalUserIdRef = useRef20(void 0);
32055
32219
  const onWithdrawSuccessFor = useCallback17(
32056
32220
  (data) => {
32057
32221
  const execution = data.execution?.data.object;
32058
32222
  track("flow_completed", {
32059
32223
  flow_type: "withdraw",
32060
32224
  amount_usd: execution?.destinationAmountUsd ?? execution?.sourceAmountUsd,
32225
+ // The one high-cardinality field we do send: the join key from a
32226
+ // funnel session back to the execution row it produced.
32227
+ execution_id: data.executionId ?? execution?.id,
32061
32228
  // Flat, low-cardinality execution dimensions for PostHog breakdowns.
32062
32229
  status: execution?.status,
32063
32230
  source_chain_type: execution?.sourceChainType,
@@ -32078,6 +32245,7 @@ function WithdrawModal({
32078
32245
  track("flow_failed", {
32079
32246
  flow_type: "withdraw",
32080
32247
  error_code: error.code,
32248
+ execution_id: execution?.id,
32081
32249
  failure_reason: execution?.failureReason ?? void 0,
32082
32250
  status: execution?.status,
32083
32251
  source_chain_type: execution?.sourceChainType,
@@ -32099,7 +32267,7 @@ function WithdrawModal({
32099
32267
  const [resolvedTheme, setResolvedTheme] = useState49(
32100
32268
  theme === "auto" ? "dark" : theme
32101
32269
  );
32102
- useEffect48(() => {
32270
+ useEffect47(() => {
32103
32271
  if (theme === "auto") {
32104
32272
  const mq = window.matchMedia("(prefers-color-scheme: dark)");
32105
32273
  setResolvedTheme(mq.matches ? "dark" : "light");
@@ -32151,6 +32319,7 @@ function WithdrawModal({
32151
32319
  ) ?? null : null;
32152
32320
  const [view, setView] = useState49("form");
32153
32321
  const [withdrawDepositWalletId, setWithdrawDepositWalletId] = useState49();
32322
+ const withdrawDepositWalletIdRef = useRef20(void 0);
32154
32323
  const [selectedExecution, setSelectedExecution] = useState49(null);
32155
32324
  const [submittedTxInfo, setSubmittedTxInfo] = useState49(null);
32156
32325
  const { executions: realtimeExecutions } = useWithdrawPolling({
@@ -32188,6 +32357,7 @@ function WithdrawModal({
32188
32357
  if (!depositWallet) {
32189
32358
  throw new Error(`No deposit wallet available for ${sourceChainType}`);
32190
32359
  }
32360
+ withdrawDepositWalletIdRef.current = depositWallet.id;
32191
32361
  setWithdrawDepositWalletId(depositWallet.id);
32192
32362
  return depositWallet;
32193
32363
  },
@@ -32207,7 +32377,12 @@ function WithdrawModal({
32207
32377
  currency: tokenData?.currency,
32208
32378
  amount: txInfo.amount,
32209
32379
  amount_base_unit: txInfo.amountBaseUnit,
32210
- ...txInfo.amountUsd ? { amount_usd: txInfo.amountUsd } : {}
32380
+ ...txInfo.amountUsd ? { amount_usd: txInfo.amountUsd } : {},
32381
+ // The execution doesn't exist yet — it's created when the user's send
32382
+ // lands on the withdraw deposit wallet, and carries this id. So a
32383
+ // withdrawal confirmed but never completed still joins to whatever
32384
+ // did (or didn't) follow on that wallet.
32385
+ deposit_wallet_id: withdrawDepositWalletIdRef.current
32211
32386
  });
32212
32387
  emitEvent({
32213
32388
  id: generatePrefixedKSUID12("sevt"),
@@ -32241,8 +32416,10 @@ function WithdrawModal({
32241
32416
  const beginJourney = useCallback17(() => {
32242
32417
  journeySessionIdRef.current = startSession();
32243
32418
  journeyActiveRef.current = true;
32419
+ journeyExternalUserIdRef.current = externalUserId;
32420
+ setUserId(null);
32244
32421
  track("widget_opened", { flow_type: "withdraw" });
32245
- }, [startSession, track]);
32422
+ }, [startSession, track, setUserId, externalUserId]);
32246
32423
  const handleClose = useCallback17(() => {
32247
32424
  finalizeJourney();
32248
32425
  onOpenChange(false);
@@ -32251,6 +32428,7 @@ function WithdrawModal({
32251
32428
  setView("form");
32252
32429
  setSelectedExecution(null);
32253
32430
  setSubmittedTxInfo(null);
32431
+ withdrawDepositWalletIdRef.current = void 0;
32254
32432
  setWithdrawDepositWalletId(void 0);
32255
32433
  resetViewTimeoutRef.current = null;
32256
32434
  }, 200);
@@ -32268,9 +32446,15 @@ function WithdrawModal({
32268
32446
  setView("form");
32269
32447
  setSelectedExecution(null);
32270
32448
  setSubmittedTxInfo(null);
32449
+ withdrawDepositWalletIdRef.current = void 0;
32271
32450
  setWithdrawDepositWalletId(void 0);
32272
32451
  }, [open]);
32273
- useEffect48(
32452
+ useLayoutEffect4(() => {
32453
+ if (!open || journeyExternalUserIdRef.current === externalUserId) return;
32454
+ journeyExternalUserIdRef.current = externalUserId;
32455
+ setUserId(null);
32456
+ }, [open, externalUserId, setUserId]);
32457
+ useEffect47(
32274
32458
  () => () => {
32275
32459
  if (journeyActiveRef.current) {
32276
32460
  journeyActiveRef.current = false;
@@ -32279,12 +32463,12 @@ function WithdrawModal({
32279
32463
  },
32280
32464
  [endSession, getSessionId]
32281
32465
  );
32282
- useEffect48(() => {
32466
+ useEffect47(() => {
32283
32467
  if (!open) return;
32284
32468
  const screen = view === "detail" ? "tracker_detail" : view;
32285
32469
  track("screen_viewed", { flow_type: "withdraw", screen });
32286
32470
  }, [open, view, track]);
32287
- useEffect48(
32471
+ useEffect47(
32288
32472
  () => () => {
32289
32473
  if (resetViewTimeoutRef.current) clearTimeout(resetViewTimeoutRef.current);
32290
32474
  },