@rhinestone/deposit-modal 0.0.0-dev-20261005152729 → 0.0.0-dev-20261006132523

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +19 -8
  2. package/dist/{BankOnrampStep-5JZU2VV7.cjs → BankOnrampStep-BJVXBEV5.cjs} +79 -100
  3. package/dist/{BankOnrampStep-2MGAO3L2.mjs → BankOnrampStep-TOE3JOPY.mjs} +75 -96
  4. package/dist/{DepositModalReown-QEXHGPJ7.mjs → DepositModalReown-2MS6WDSA.mjs} +3 -3
  5. package/dist/{DepositModalReown-7ARAFK5D.cjs → DepositModalReown-DNFBKJDD.cjs} +16 -16
  6. package/dist/WithdrawModal-HJ7BBSUK.cjs +12 -0
  7. package/dist/{WithdrawModal-SIVVBR2V.mjs → WithdrawModal-SC2LBNOS.mjs} +3 -3
  8. package/dist/{bearer-transport-CVHNBgoV.d.cts → bearer-transport-DZyeDG64.d.cts} +5 -1
  9. package/dist/{bearer-transport-CVHNBgoV.d.ts → bearer-transport-DZyeDG64.d.ts} +5 -1
  10. package/dist/{chunk-ERIMNOQF.mjs → chunk-3R3A7OYL.mjs} +8 -3
  11. package/dist/{chunk-ZO745YKG.cjs → chunk-5ACTCWIE.cjs} +1 -1
  12. package/dist/{chunk-IE62LNIK.mjs → chunk-7I2SG3KU.mjs} +1 -1
  13. package/dist/{chunk-6OFZPWBL.cjs → chunk-AXCGY2TG.cjs} +2 -2
  14. package/dist/{chunk-4Y7VO7E6.cjs → chunk-BAVRVXLV.cjs} +5 -5
  15. package/dist/{chunk-CCDDZTYW.cjs → chunk-DSM4IHTM.cjs} +10 -5
  16. package/dist/{chunk-3N33UIXN.cjs → chunk-FEQQJCJR.cjs} +286 -281
  17. package/dist/{chunk-MPGJAZQS.cjs → chunk-FO6P7HJA.cjs} +48 -33
  18. package/dist/{chunk-UQ6JWKI6.mjs → chunk-GR23TV4U.mjs} +3 -3
  19. package/dist/{chunk-5ZQCRQ7R.cjs → chunk-GRUBTFN4.cjs} +58 -58
  20. package/dist/{chunk-STHQNQSI.mjs → chunk-HQDTWHSI.mjs} +1 -1
  21. package/dist/{chunk-RHMWW357.mjs → chunk-SD733OIA.mjs} +17 -2
  22. package/dist/{chunk-6KNIVGZM.mjs → chunk-T255BMBC.mjs} +1 -1
  23. package/dist/{chunk-2ITR5NSI.mjs → chunk-WOOAUDHW.mjs} +58 -53
  24. package/dist/compliance.cjs +4 -4
  25. package/dist/compliance.d.cts +2 -2
  26. package/dist/compliance.d.ts +2 -2
  27. package/dist/compliance.mjs +3 -3
  28. package/dist/constants.cjs +2 -2
  29. package/dist/constants.d.cts +1 -1
  30. package/dist/constants.d.ts +1 -1
  31. package/dist/constants.mjs +1 -1
  32. package/dist/deposit.cjs +4 -4
  33. package/dist/deposit.d.cts +3 -3
  34. package/dist/deposit.d.ts +3 -3
  35. package/dist/deposit.mjs +3 -3
  36. package/dist/embed.cjs +14 -14
  37. package/dist/embed.d.cts +2 -3
  38. package/dist/embed.d.ts +2 -3
  39. package/dist/embed.mjs +4 -4
  40. package/dist/index.cjs +5 -5
  41. package/dist/index.d.cts +2 -2
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.mjs +4 -4
  44. package/dist/onramp.cjs +4 -4
  45. package/dist/onramp.d.cts +2 -2
  46. package/dist/onramp.d.ts +2 -2
  47. package/dist/onramp.mjs +3 -3
  48. package/dist/{types-Bb-X7UNj.d.ts → types-BxSrazbq.d.ts} +1 -4
  49. package/dist/{types-DwGSrnlX.d.cts → types-CixUBhrM.d.cts} +1 -4
  50. package/dist/withdraw.cjs +4 -4
  51. package/dist/withdraw.d.cts +3 -4
  52. package/dist/withdraw.d.ts +3 -4
  53. package/dist/withdraw.mjs +3 -3
  54. package/package.json +1 -1
  55. package/dist/WithdrawModal-BU5WWC45.cjs +0 -12
package/README.md CHANGED
@@ -326,9 +326,12 @@ frame-src https://widget.swapped.com https://sandbox.swapped.com https://connect
326
326
 
327
327
  ### Bank transfer accounts (Noah)
328
328
 
329
- > **Preview.** Your proxy must forward `POST /compliance/verification` on the customer
330
- > bearer; deploy it before shipping a modal that calls it, or first-time
331
- > verification 404s at the proxy.
329
+ > **Preview.** Your proxy must forward these on the customer bearer before you ship
330
+ > a modal with `bankOnramp`: `GET /compliance/status`, `POST /compliance/verification`,
331
+ > `GET /onramp/options`, `POST`/`GET /onramp/accounts`,
332
+ > `GET /onramp/accounts/:id[/details]` and `GET /onramp/orders[/:id]`. A missing one
333
+ > 404s at the proxy. [`deposit-widget-proxy`](https://github.com/rhinestonewtf/deposit-widget-proxy)
334
+ > forwards all of them.
332
335
  >
333
336
  > The prop is opt-in and inert unless you pass it. Treat it as unstable until
334
337
  > this note is removed.
@@ -337,10 +340,18 @@ Pass `bankOnramp` to use reusable USD/EUR account details when Noah is provision
337
340
  for your organization. Integrator KYB happens directly with Noah; Rhinestone
338
341
  enables provisioning after Noah confirms it. Card and Apple Pay continue through Swapped. A customer who has
339
342
  not been verified is taken through hosted verification first; an existing active
340
- account is resumed; otherwise the selected currency is configured once.
343
+ account is resumed; otherwise the account is configured once. There is no account
344
+ selection: the customer's one account opens directly.
345
+
346
+ Rhinestone chooses the currency from the residence the customer declares during
347
+ verification: EUR inside SEPA, USD elsewhere. It is not configurable, and it does
348
+ not follow the customer's location. The options are read again once the customer
349
+ is approved, because only then is the residence known.
341
350
 
342
351
  The modal checks `GET /onramp/options?provider=noah` before customer verification. A 404
343
- (provisioning disabled or an older proxy) routes bank transfer through Swapped.
352
+ (provisioning disabled, a declared residence Noah doesn't serve, or an older proxy)
353
+ routes bank transfer through Swapped. When Swapped's regional list has no bank transfer
354
+ either, the customer returns to the Cash methods with a notice and the row hidden.
344
355
  A successful response keeps Noah exclusive, even when no currencies are available.
345
356
  Authentication and service errors show a retry instead of switching providers.
346
357
 
@@ -363,7 +374,6 @@ chain. No on-chain link is shown for a fiat-only or sub-fee transfer.
363
374
  ```tsx
364
375
  <DepositModal
365
376
  bankOnramp={{
366
- currency: "USD", // Optional; omit to show every available currency.
367
377
  supportUrl: "https://your-app.example/support", // Optional bank-transfer help.
368
378
  getSession: (smartAccount) =>
369
379
  authenticatedSessionClient.getCustomerSession({ smartAccount }),
@@ -487,8 +497,9 @@ const options = {
487
497
  getSession: () => authenticatedSessionClient.getCustomerSession(),
488
498
  };
489
499
  const onramp = createOnrampService(options);
490
- const { options: available } = await onramp.getOptions({ currency: 'EUR' });
491
- const { account } = await onramp.createAccount({ currency: 'EUR' });
500
+ // Exactly one option, by region: EUR inside SEPA, USD elsewhere.
501
+ const { options: available } = await onramp.getOptions();
502
+ const { account } = await onramp.createAccount({ currency: available[0].currency });
492
503
  // Setup completes in this call and returns the account it configured.
493
504
  // Show the customer their bank details through a separate, repeatable call:
494
505
  const { bankDetails } = await onramp.getAccountDetails(account.id);
@@ -8,15 +8,15 @@
8
8
  var _chunkG264OM23cjs = require('./chunk-G264OM23.cjs');
9
9
 
10
10
 
11
- var _chunk6OFZPWBLcjs = require('./chunk-6OFZPWBL.cjs');
11
+ var _chunkAXCGY2TGcjs = require('./chunk-AXCGY2TG.cjs');
12
12
 
13
13
 
14
14
 
15
- var _chunk4Y7VO7E6cjs = require('./chunk-4Y7VO7E6.cjs');
15
+ var _chunkBAVRVXLVcjs = require('./chunk-BAVRVXLV.cjs');
16
16
 
17
17
 
18
- var _chunkCCDDZTYWcjs = require('./chunk-CCDDZTYW.cjs');
19
- require('./chunk-ZO745YKG.cjs');
18
+ var _chunkDSM4IHTMcjs = require('./chunk-DSM4IHTM.cjs');
19
+ require('./chunk-5ACTCWIE.cjs');
20
20
 
21
21
  // src/components/steps/BankOnrampStep.tsx
22
22
  var _react = require('react');
@@ -128,25 +128,9 @@ var OPTIONS_ERROR = "Bank transfer options are unavailable. Try again.";
128
128
  var VERIFICATION_POLL_MS = 5e3;
129
129
  var MAX_TIMEOUT_MS = 2147483647;
130
130
  function BankOnrampStep({
131
- unavailableFallback,
132
- ...props
133
- }) {
134
- const [unavailable, setUnavailable] = _react.useState.call(void 0, false);
135
- const handleUnavailable = _react.useCallback.call(void 0, () => setUnavailable(true), []);
136
- if (unavailable) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: unavailableFallback });
137
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
138
- NoahOnrampStep,
139
- {
140
- ...props,
141
- onUnavailable: unavailableFallback ? handleUnavailable : void 0
142
- }
143
- );
144
- }
145
- function NoahOnrampStep({
146
131
  backendUrl,
147
132
  smartAccount,
148
133
  bankOnramp,
149
- onInternalBack,
150
134
  onError,
151
135
  openExternally,
152
136
  onUnavailable,
@@ -161,7 +145,7 @@ function NoahOnrampStep({
161
145
  [backendUrl, getSessionRef, smartAccount]
162
146
  );
163
147
  const service = _react.useMemo.call(void 0,
164
- () => _chunk6OFZPWBLcjs.createOnrampService.call(void 0, sessionOptions),
148
+ () => _chunkAXCGY2TGcjs.createOnrampService.call(void 0, sessionOptions),
165
149
  [sessionOptions]
166
150
  );
167
151
  const [availableForHistory, setAvailableForHistory] = _react.useState.call(void 0, false);
@@ -169,12 +153,15 @@ function NoahOnrampStep({
169
153
  if (availableForHistory) _optionalChain([onHistorySource, 'optionalCall', _ => _(service)]);
170
154
  return () => _optionalChain([onHistorySource, 'optionalCall', _2 => _2(null)]);
171
155
  }, [service, onHistorySource, availableForHistory]);
172
- const [options, setOptions] = _react.useState.call(void 0, []);
173
- const [accounts, setAccounts] = _react.useState.call(void 0, []);
156
+ const [noOptions, setNoOptions] = _react.useState.call(void 0, false);
174
157
  const [loading, setLoading] = _react.useState.call(void 0, true);
175
- const [selectedAccount, setSelectedAccount] = _react.useState.call(void 0, null);
158
+ const [selectedAccount, setSelectedAccount] = _react.useState.call(void 0,
159
+ null
160
+ );
176
161
  const [hostedUrl, setHostedUrl] = _react.useState.call(void 0, null);
177
- const [bankDetails, setBankDetails] = _react.useState.call(void 0, null);
162
+ const [bankDetails, setBankDetails] = _react.useState.call(void 0,
163
+ null
164
+ );
178
165
  const [verificationExpired, setVerificationExpired] = _react.useState.call(void 0, false);
179
166
  const [verificationExpiresAt, setVerificationExpiresAt] = _react.useState.call(void 0, null);
180
167
  const [iframeLoaded, setIframeLoaded] = _react.useState.call(void 0, false);
@@ -182,19 +169,13 @@ function NoahOnrampStep({
182
169
  const [error, setError] = _react.useState.call(void 0, null);
183
170
  const [verifying, setVerifying] = _react.useState.call(void 0, null);
184
171
  const requestGenerationRef = _react.useRef.call(void 0, 0);
172
+ const verificationResumedRef = _react.useRef.call(void 0, false);
185
173
  const isCurrentRequest = _react.useCallback.call(void 0,
186
174
  (generation) => requestGenerationRef.current === generation,
187
175
  []
188
176
  );
189
- const ensureVerified = _react.useCallback.call(void 0, async () => {
190
- const current = await _chunk4Y7VO7E6cjs.getComplianceStatus.call(void 0, sessionOptions);
191
- if (current.status === "approved") return "approved";
192
- if (current.status === "declined") return "declined";
193
- const session = await _chunk4Y7VO7E6cjs.startComplianceVerification.call(void 0, sessionOptions, {
194
- // Verification is per fiat currency. Without an explicit choice the US
195
- // dollar journey is the one every configured country can complete.
196
- currency: _nullishCoalesce(bankOnramp.currency, () => ( "USD"))
197
- });
177
+ const startVerification = _react.useCallback.call(void 0, async () => {
178
+ const session = await _chunkBAVRVXLVcjs.startComplianceVerification.call(void 0, sessionOptions);
198
179
  if (session.status === "failed") return "declined";
199
180
  if (!session.nextAction) return "reviewing";
200
181
  if (!isTrustedNoahHostedUrl(session.nextAction.url)) return "reviewing";
@@ -208,21 +189,27 @@ function NoahOnrampStep({
208
189
  setVerificationExpired(false);
209
190
  setIframeLoaded(false);
210
191
  return "action_required";
211
- }, [bankOnramp.currency, openExternally, sessionOptions]);
192
+ }, [openExternally, sessionOptions]);
193
+ const ensureVerified = _react.useCallback.call(void 0, async () => {
194
+ const current = await _chunkBAVRVXLVcjs.getComplianceStatus.call(void 0, sessionOptions);
195
+ if (current.status === "approved") return "approved";
196
+ if (current.status === "declined") return "declined";
197
+ return startVerification();
198
+ }, [sessionOptions, startVerification]);
212
199
  const loadAccounts = _react.useCallback.call(void 0, async () => {
213
200
  const generation = ++requestGenerationRef.current;
214
201
  setLoading(true);
215
202
  setError(null);
203
+ setNoOptions(false);
216
204
  try {
217
- const available = await service.getOptions({
218
- ...bankOnramp.currency ? { currency: bankOnramp.currency } : {}
219
- }).catch((error2) => {
220
- if (error2 instanceof _chunkCCDDZTYWcjs.ScopedRequestError && error2.status === 404 && onUnavailable) {
205
+ const discover = () => service.getOptions().catch((error2) => {
206
+ if (error2 instanceof _chunkDSM4IHTMcjs.ScopedRequestError && error2.status === 404 && onUnavailable) {
221
207
  if (isCurrentRequest(generation)) onUnavailable();
222
208
  return null;
223
209
  }
224
210
  throw error2;
225
211
  });
212
+ const available = await discover();
226
213
  if (!available || !isCurrentRequest(generation)) return;
227
214
  setAvailableForHistory(true);
228
215
  const verification = await ensureVerified();
@@ -233,10 +220,22 @@ function NoahOnrampStep({
233
220
  }
234
221
  setVerifying(null);
235
222
  setExternalOpened(false);
236
- const existing = await service.listAccounts();
237
- if (!isCurrentRequest(generation)) return;
238
- setOptions(available.options);
239
- setAccounts(existing.items);
223
+ const [current, existing] = await Promise.all([
224
+ discover(),
225
+ service.listAccounts()
226
+ ]);
227
+ if (!current || !isCurrentRequest(generation)) return;
228
+ const [option] = current.options;
229
+ if (!option) {
230
+ setNoOptions(true);
231
+ return;
232
+ }
233
+ void selectOptionRef.current(
234
+ option,
235
+ existing.items.find(
236
+ (account) => account.status === "active" && account.currency === option.currency
237
+ )
238
+ );
240
239
  } catch (e3) {
241
240
  if (!isCurrentRequest(generation)) return;
242
241
  setError(OPTIONS_ERROR);
@@ -244,14 +243,7 @@ function NoahOnrampStep({
244
243
  } finally {
245
244
  if (isCurrentRequest(generation)) setLoading(false);
246
245
  }
247
- }, [
248
- bankOnramp.currency,
249
- ensureVerified,
250
- isCurrentRequest,
251
- onError,
252
- onUnavailable,
253
- service
254
- ]);
246
+ }, [ensureVerified, isCurrentRequest, onError, onUnavailable, service]);
255
247
  _react.useEffect.call(void 0, () => {
256
248
  setSelectedAccount(null);
257
249
  setBankDetails(null);
@@ -269,16 +261,22 @@ function NoahOnrampStep({
269
261
  setError(null);
270
262
  setBankDetails(null);
271
263
  try {
272
- const { bankDetails: details } = await service.getAccountDetails(account.id);
264
+ const { bankDetails: details } = await service.getAccountDetails(
265
+ account.id
266
+ );
273
267
  if (!isCurrentRequest(generation)) return;
274
- if (details.currency !== account.currency) throw new Error("Bank currency mismatch");
268
+ if (details.currency !== account.currency)
269
+ throw new Error("Bank currency mismatch");
275
270
  setBankDetails(details);
276
271
  } catch (error2) {
277
272
  if (!isCurrentRequest(generation)) return;
278
- const pending = error2 instanceof _chunkCCDDZTYWcjs.ScopedRequestError && error2.status === 409;
273
+ const pending = error2 instanceof _chunkDSM4IHTMcjs.ScopedRequestError && error2.status === 409;
279
274
  const message = pending ? PENDING_ERROR : SAFE_ERROR;
280
275
  setError(message);
281
- _optionalChain([onError, 'optionalCall', _4 => _4(message, pending ? "BANK_ONRAMP_DETAILS_PENDING" : "BANK_ONRAMP_DETAILS_FAILED")]);
276
+ _optionalChain([onError, 'optionalCall', _4 => _4(
277
+ message,
278
+ pending ? "BANK_ONRAMP_DETAILS_PENDING" : "BANK_ONRAMP_DETAILS_FAILED"
279
+ )]);
282
280
  } finally {
283
281
  if (isCurrentRequest(generation)) setLoading(false);
284
282
  }
@@ -304,27 +302,28 @@ function NoahOnrampStep({
304
302
  if (!isCurrentRequest(generation)) return;
305
303
  setSelectedAccount(nextAccount);
306
304
  await openDetails(nextAccount);
307
- } catch (e4) {
305
+ } catch (error2) {
308
306
  if (!isCurrentRequest(generation)) return;
307
+ if (error2 instanceof _chunkDSM4IHTMcjs.ScopedRequestError && error2.status === 409 && error2.code === "VERIFICATION_REQUIRED" && !verificationResumedRef.current) {
308
+ verificationResumedRef.current = true;
309
+ try {
310
+ const verification = await startVerification();
311
+ if (!isCurrentRequest(generation)) return;
312
+ setVerifying(verification);
313
+ setLoading(false);
314
+ return;
315
+ } catch (e4) {
316
+ if (!isCurrentRequest(generation)) return;
317
+ }
318
+ }
309
319
  setError(SAFE_ERROR);
310
320
  _optionalChain([onError, 'optionalCall', _5 => _5(SAFE_ERROR, "BANK_ONRAMP_SETUP_FAILED")]);
311
321
  setLoading(false);
312
322
  }
313
323
  },
314
- [isCurrentRequest, onError, openDetails, service]
324
+ [isCurrentRequest, onError, openDetails, service, startVerification]
315
325
  );
316
- const backToCurrencies = _react.useCallback.call(void 0, () => {
317
- requestGenerationRef.current += 1;
318
- setSelectedAccount(null);
319
- setBankDetails(null);
320
- setError(null);
321
- setLoading(false);
322
- }, []);
323
- _react.useEffect.call(void 0, () => {
324
- if (!onInternalBack) return;
325
- onInternalBack(selectedAccount ? backToCurrencies : null);
326
- return () => onInternalBack(null);
327
- }, [backToCurrencies, onInternalBack, selectedAccount]);
326
+ const selectOptionRef = _chunkG264OM23cjs.useLatestRef.call(void 0, selectOption);
328
327
  _react.useEffect.call(void 0, () => {
329
328
  if (verifying !== "action_required" || !hostedUrl) return;
330
329
  const remaining = hostedSessionRemainingMs(
@@ -341,7 +340,7 @@ function NoahOnrampStep({
341
340
  const timer = setInterval(() => {
342
341
  void (async () => {
343
342
  try {
344
- const status = await _chunk4Y7VO7E6cjs.getComplianceStatus.call(void 0, sessionOptions);
343
+ const status = await _chunkBAVRVXLVcjs.getComplianceStatus.call(void 0, sessionOptions);
345
344
  if (!active) return;
346
345
  if (status.status === "approved") {
347
346
  setHostedUrl(null);
@@ -371,7 +370,7 @@ function NoahOnrampStep({
371
370
  {
372
371
  icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkG264OM23cjs.BankIcon, {}),
373
372
  title: "Bank transfer",
374
- subtitle: bankDetails ? "Your bank transfer details" : "Choose an account to view transfer details"
373
+ subtitle: "Your bank transfer details"
375
374
  }
376
375
  ),
377
376
  loading && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "rs-loading-state", "aria-live": "polite", children: [
@@ -406,7 +405,14 @@ function NoahOnrampStep({
406
405
  ) })
407
406
  ] }),
408
407
  !loading && !error && verifying === "action_required" && !verificationExpired && hostedUrl && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "rs-bank-onramp-hosted", children: [
409
- !iframeLoaded && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "rs-bank-onramp-frame-loading", "aria-live": "polite", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkG264OM23cjs.Spinner, {}) }),
408
+ !iframeLoaded && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
409
+ "div",
410
+ {
411
+ className: "rs-bank-onramp-frame-loading",
412
+ "aria-live": "polite",
413
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkG264OM23cjs.Spinner, {})
414
+ }
415
+ ),
410
416
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
411
417
  "iframe",
412
418
  {
@@ -419,34 +425,7 @@ function NoahOnrampStep({
419
425
  }
420
426
  )
421
427
  ] }),
422
- !loading && !error && !verifying && !selectedAccount && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "rs-list", children: [
423
- options.map((option) => {
424
- const account = accounts.find(
425
- (candidate) => candidate.status === "active" && candidate.currency === option.currency
426
- );
427
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
428
- "button",
429
- {
430
- type: "button",
431
- className: "rs-list-row",
432
- onClick: () => void selectOption(option, account),
433
- "aria-label": `${account ? "View" : "Set up"} ${option.currency} bank account`,
434
- children: [
435
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "rs-list-row-leading", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkG264OM23cjs.BankIcon, {}) }),
436
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "rs-list-row-body", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "rs-list-row-text", children: [
437
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "rs-list-row-title", children: [
438
- option.currency,
439
- " account"
440
- ] }),
441
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "rs-list-row-subtitle", children: account ? "View bank details" : "Set up bank details" })
442
- ] }) })
443
- ]
444
- },
445
- option.currency
446
- );
447
- }),
448
- options.length === 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "rs-loading-state", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "rs-loading-title", children: "No bank transfer options are available." }) })
449
- ] }),
428
+ !loading && !error && !verifying && noOptions && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "rs-loading-state", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "rs-loading-title", children: "No bank transfer options are available." }) }),
450
429
  !loading && !error && !verifying && selectedAccount && bankDetails && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, BankDetailsView, { details: bankDetails }, selectedAccount.id) })
451
430
  ] }) });
452
431
  }
@@ -8,15 +8,15 @@ import {
8
8
  } from "./chunk-YY4ZOAH5.mjs";
9
9
  import {
10
10
  createOnrampService
11
- } from "./chunk-6KNIVGZM.mjs";
11
+ } from "./chunk-T255BMBC.mjs";
12
12
  import {
13
13
  getComplianceStatus,
14
14
  startComplianceVerification
15
- } from "./chunk-UQ6JWKI6.mjs";
15
+ } from "./chunk-GR23TV4U.mjs";
16
16
  import {
17
17
  ScopedRequestError
18
- } from "./chunk-ERIMNOQF.mjs";
19
- import "./chunk-STHQNQSI.mjs";
18
+ } from "./chunk-3R3A7OYL.mjs";
19
+ import "./chunk-HQDTWHSI.mjs";
20
20
 
21
21
  // src/components/steps/BankOnrampStep.tsx
22
22
  import { useCallback, useEffect, useMemo, useRef, useState as useState2 } from "react";
@@ -128,25 +128,9 @@ var OPTIONS_ERROR = "Bank transfer options are unavailable. Try again.";
128
128
  var VERIFICATION_POLL_MS = 5e3;
129
129
  var MAX_TIMEOUT_MS = 2147483647;
130
130
  function BankOnrampStep({
131
- unavailableFallback,
132
- ...props
133
- }) {
134
- const [unavailable, setUnavailable] = useState2(false);
135
- const handleUnavailable = useCallback(() => setUnavailable(true), []);
136
- if (unavailable) return /* @__PURE__ */ jsx2(Fragment, { children: unavailableFallback });
137
- return /* @__PURE__ */ jsx2(
138
- NoahOnrampStep,
139
- {
140
- ...props,
141
- onUnavailable: unavailableFallback ? handleUnavailable : void 0
142
- }
143
- );
144
- }
145
- function NoahOnrampStep({
146
131
  backendUrl,
147
132
  smartAccount,
148
133
  bankOnramp,
149
- onInternalBack,
150
134
  onError,
151
135
  openExternally,
152
136
  onUnavailable,
@@ -169,12 +153,15 @@ function NoahOnrampStep({
169
153
  if (availableForHistory) onHistorySource?.(service);
170
154
  return () => onHistorySource?.(null);
171
155
  }, [service, onHistorySource, availableForHistory]);
172
- const [options, setOptions] = useState2([]);
173
- const [accounts, setAccounts] = useState2([]);
156
+ const [noOptions, setNoOptions] = useState2(false);
174
157
  const [loading, setLoading] = useState2(true);
175
- const [selectedAccount, setSelectedAccount] = useState2(null);
158
+ const [selectedAccount, setSelectedAccount] = useState2(
159
+ null
160
+ );
176
161
  const [hostedUrl, setHostedUrl] = useState2(null);
177
- const [bankDetails, setBankDetails] = useState2(null);
162
+ const [bankDetails, setBankDetails] = useState2(
163
+ null
164
+ );
178
165
  const [verificationExpired, setVerificationExpired] = useState2(false);
179
166
  const [verificationExpiresAt, setVerificationExpiresAt] = useState2(null);
180
167
  const [iframeLoaded, setIframeLoaded] = useState2(false);
@@ -182,19 +169,13 @@ function NoahOnrampStep({
182
169
  const [error, setError] = useState2(null);
183
170
  const [verifying, setVerifying] = useState2(null);
184
171
  const requestGenerationRef = useRef(0);
172
+ const verificationResumedRef = useRef(false);
185
173
  const isCurrentRequest = useCallback(
186
174
  (generation) => requestGenerationRef.current === generation,
187
175
  []
188
176
  );
189
- const ensureVerified = useCallback(async () => {
190
- const current = await getComplianceStatus(sessionOptions);
191
- if (current.status === "approved") return "approved";
192
- if (current.status === "declined") return "declined";
193
- const session = await startComplianceVerification(sessionOptions, {
194
- // Verification is per fiat currency. Without an explicit choice the US
195
- // dollar journey is the one every configured country can complete.
196
- currency: bankOnramp.currency ?? "USD"
197
- });
177
+ const startVerification = useCallback(async () => {
178
+ const session = await startComplianceVerification(sessionOptions);
198
179
  if (session.status === "failed") return "declined";
199
180
  if (!session.nextAction) return "reviewing";
200
181
  if (!isTrustedNoahHostedUrl(session.nextAction.url)) return "reviewing";
@@ -208,21 +189,27 @@ function NoahOnrampStep({
208
189
  setVerificationExpired(false);
209
190
  setIframeLoaded(false);
210
191
  return "action_required";
211
- }, [bankOnramp.currency, openExternally, sessionOptions]);
192
+ }, [openExternally, sessionOptions]);
193
+ const ensureVerified = useCallback(async () => {
194
+ const current = await getComplianceStatus(sessionOptions);
195
+ if (current.status === "approved") return "approved";
196
+ if (current.status === "declined") return "declined";
197
+ return startVerification();
198
+ }, [sessionOptions, startVerification]);
212
199
  const loadAccounts = useCallback(async () => {
213
200
  const generation = ++requestGenerationRef.current;
214
201
  setLoading(true);
215
202
  setError(null);
203
+ setNoOptions(false);
216
204
  try {
217
- const available = await service.getOptions({
218
- ...bankOnramp.currency ? { currency: bankOnramp.currency } : {}
219
- }).catch((error2) => {
205
+ const discover = () => service.getOptions().catch((error2) => {
220
206
  if (error2 instanceof ScopedRequestError && error2.status === 404 && onUnavailable) {
221
207
  if (isCurrentRequest(generation)) onUnavailable();
222
208
  return null;
223
209
  }
224
210
  throw error2;
225
211
  });
212
+ const available = await discover();
226
213
  if (!available || !isCurrentRequest(generation)) return;
227
214
  setAvailableForHistory(true);
228
215
  const verification = await ensureVerified();
@@ -233,10 +220,22 @@ function NoahOnrampStep({
233
220
  }
234
221
  setVerifying(null);
235
222
  setExternalOpened(false);
236
- const existing = await service.listAccounts();
237
- if (!isCurrentRequest(generation)) return;
238
- setOptions(available.options);
239
- setAccounts(existing.items);
223
+ const [current, existing] = await Promise.all([
224
+ discover(),
225
+ service.listAccounts()
226
+ ]);
227
+ if (!current || !isCurrentRequest(generation)) return;
228
+ const [option] = current.options;
229
+ if (!option) {
230
+ setNoOptions(true);
231
+ return;
232
+ }
233
+ void selectOptionRef.current(
234
+ option,
235
+ existing.items.find(
236
+ (account) => account.status === "active" && account.currency === option.currency
237
+ )
238
+ );
240
239
  } catch {
241
240
  if (!isCurrentRequest(generation)) return;
242
241
  setError(OPTIONS_ERROR);
@@ -244,14 +243,7 @@ function NoahOnrampStep({
244
243
  } finally {
245
244
  if (isCurrentRequest(generation)) setLoading(false);
246
245
  }
247
- }, [
248
- bankOnramp.currency,
249
- ensureVerified,
250
- isCurrentRequest,
251
- onError,
252
- onUnavailable,
253
- service
254
- ]);
246
+ }, [ensureVerified, isCurrentRequest, onError, onUnavailable, service]);
255
247
  useEffect(() => {
256
248
  setSelectedAccount(null);
257
249
  setBankDetails(null);
@@ -269,16 +261,22 @@ function NoahOnrampStep({
269
261
  setError(null);
270
262
  setBankDetails(null);
271
263
  try {
272
- const { bankDetails: details } = await service.getAccountDetails(account.id);
264
+ const { bankDetails: details } = await service.getAccountDetails(
265
+ account.id
266
+ );
273
267
  if (!isCurrentRequest(generation)) return;
274
- if (details.currency !== account.currency) throw new Error("Bank currency mismatch");
268
+ if (details.currency !== account.currency)
269
+ throw new Error("Bank currency mismatch");
275
270
  setBankDetails(details);
276
271
  } catch (error2) {
277
272
  if (!isCurrentRequest(generation)) return;
278
273
  const pending = error2 instanceof ScopedRequestError && error2.status === 409;
279
274
  const message = pending ? PENDING_ERROR : SAFE_ERROR;
280
275
  setError(message);
281
- onError?.(message, pending ? "BANK_ONRAMP_DETAILS_PENDING" : "BANK_ONRAMP_DETAILS_FAILED");
276
+ onError?.(
277
+ message,
278
+ pending ? "BANK_ONRAMP_DETAILS_PENDING" : "BANK_ONRAMP_DETAILS_FAILED"
279
+ );
282
280
  } finally {
283
281
  if (isCurrentRequest(generation)) setLoading(false);
284
282
  }
@@ -304,27 +302,28 @@ function NoahOnrampStep({
304
302
  if (!isCurrentRequest(generation)) return;
305
303
  setSelectedAccount(nextAccount);
306
304
  await openDetails(nextAccount);
307
- } catch {
305
+ } catch (error2) {
308
306
  if (!isCurrentRequest(generation)) return;
307
+ if (error2 instanceof ScopedRequestError && error2.status === 409 && error2.code === "VERIFICATION_REQUIRED" && !verificationResumedRef.current) {
308
+ verificationResumedRef.current = true;
309
+ try {
310
+ const verification = await startVerification();
311
+ if (!isCurrentRequest(generation)) return;
312
+ setVerifying(verification);
313
+ setLoading(false);
314
+ return;
315
+ } catch {
316
+ if (!isCurrentRequest(generation)) return;
317
+ }
318
+ }
309
319
  setError(SAFE_ERROR);
310
320
  onError?.(SAFE_ERROR, "BANK_ONRAMP_SETUP_FAILED");
311
321
  setLoading(false);
312
322
  }
313
323
  },
314
- [isCurrentRequest, onError, openDetails, service]
324
+ [isCurrentRequest, onError, openDetails, service, startVerification]
315
325
  );
316
- const backToCurrencies = useCallback(() => {
317
- requestGenerationRef.current += 1;
318
- setSelectedAccount(null);
319
- setBankDetails(null);
320
- setError(null);
321
- setLoading(false);
322
- }, []);
323
- useEffect(() => {
324
- if (!onInternalBack) return;
325
- onInternalBack(selectedAccount ? backToCurrencies : null);
326
- return () => onInternalBack(null);
327
- }, [backToCurrencies, onInternalBack, selectedAccount]);
326
+ const selectOptionRef = useLatestRef(selectOption);
328
327
  useEffect(() => {
329
328
  if (verifying !== "action_required" || !hostedUrl) return;
330
329
  const remaining = hostedSessionRemainingMs(
@@ -371,7 +370,7 @@ function NoahOnrampStep({
371
370
  {
372
371
  icon: /* @__PURE__ */ jsx2(BankIcon, {}),
373
372
  title: "Bank transfer",
374
- subtitle: bankDetails ? "Your bank transfer details" : "Choose an account to view transfer details"
373
+ subtitle: "Your bank transfer details"
375
374
  }
376
375
  ),
377
376
  loading && /* @__PURE__ */ jsxs2("div", { className: "rs-loading-state", "aria-live": "polite", children: [
@@ -406,7 +405,14 @@ function NoahOnrampStep({
406
405
  ) })
407
406
  ] }),
408
407
  !loading && !error && verifying === "action_required" && !verificationExpired && hostedUrl && /* @__PURE__ */ jsxs2("div", { className: "rs-bank-onramp-hosted", children: [
409
- !iframeLoaded && /* @__PURE__ */ jsx2("div", { className: "rs-bank-onramp-frame-loading", "aria-live": "polite", children: /* @__PURE__ */ jsx2(Spinner, {}) }),
408
+ !iframeLoaded && /* @__PURE__ */ jsx2(
409
+ "div",
410
+ {
411
+ className: "rs-bank-onramp-frame-loading",
412
+ "aria-live": "polite",
413
+ children: /* @__PURE__ */ jsx2(Spinner, {})
414
+ }
415
+ ),
410
416
  /* @__PURE__ */ jsx2(
411
417
  "iframe",
412
418
  {
@@ -419,34 +425,7 @@ function NoahOnrampStep({
419
425
  }
420
426
  )
421
427
  ] }),
422
- !loading && !error && !verifying && !selectedAccount && /* @__PURE__ */ jsxs2("div", { className: "rs-list", children: [
423
- options.map((option) => {
424
- const account = accounts.find(
425
- (candidate) => candidate.status === "active" && candidate.currency === option.currency
426
- );
427
- return /* @__PURE__ */ jsxs2(
428
- "button",
429
- {
430
- type: "button",
431
- className: "rs-list-row",
432
- onClick: () => void selectOption(option, account),
433
- "aria-label": `${account ? "View" : "Set up"} ${option.currency} bank account`,
434
- children: [
435
- /* @__PURE__ */ jsx2("span", { className: "rs-list-row-leading", children: /* @__PURE__ */ jsx2(BankIcon, {}) }),
436
- /* @__PURE__ */ jsx2("span", { className: "rs-list-row-body", children: /* @__PURE__ */ jsxs2("span", { className: "rs-list-row-text", children: [
437
- /* @__PURE__ */ jsxs2("span", { className: "rs-list-row-title", children: [
438
- option.currency,
439
- " account"
440
- ] }),
441
- /* @__PURE__ */ jsx2("span", { className: "rs-list-row-subtitle", children: account ? "View bank details" : "Set up bank details" })
442
- ] }) })
443
- ]
444
- },
445
- option.currency
446
- );
447
- }),
448
- options.length === 0 && /* @__PURE__ */ jsx2("div", { className: "rs-loading-state", children: /* @__PURE__ */ jsx2("div", { className: "rs-loading-title", children: "No bank transfer options are available." }) })
449
- ] }),
428
+ !loading && !error && !verifying && noOptions && /* @__PURE__ */ jsx2("div", { className: "rs-loading-state", children: /* @__PURE__ */ jsx2("div", { className: "rs-loading-title", children: "No bank transfer options are available." }) }),
450
429
  !loading && !error && !verifying && selectedAccount && bankDetails && /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2(BankDetailsView, { details: bankDetails }, selectedAccount.id) })
451
430
  ] }) });
452
431
  }