@bounded-sh/client 0.0.59 → 0.0.61

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 (54) hide show
  1. package/dist/{email-auth-provider-N-dfsNnE.js → email-auth-provider-9BwSziWj.js} +2 -2
  2. package/dist/{email-auth-provider-N-dfsNnE.js.map → email-auth-provider-9BwSziWj.js.map} +1 -1
  3. package/dist/{email-auth-provider-DhAMOILG.mjs → email-auth-provider-B3XaJw-G.mjs} +2 -2
  4. package/dist/{email-auth-provider-deB_ADc5.mjs.map → email-auth-provider-B3XaJw-G.mjs.map} +1 -1
  5. package/dist/{email-auth-provider-BSw9FRjg.js → email-auth-provider-BttT2sZK.js} +2 -2
  6. package/dist/{email-auth-provider-BSw9FRjg.js.map → email-auth-provider-BttT2sZK.js.map} +1 -1
  7. package/dist/{email-auth-provider-deB_ADc5.mjs → email-auth-provider-Cb4VKA1E.mjs} +2 -2
  8. package/dist/{email-auth-provider-DhAMOILG.mjs.map → email-auth-provider-Cb4VKA1E.mjs.map} +1 -1
  9. package/dist/{guest-auth-provider-D9wbogwi.mjs → guest-auth-provider-BBPpydtM.mjs} +2 -2
  10. package/dist/{guest-auth-provider-B78Ugfar.mjs.map → guest-auth-provider-BBPpydtM.mjs.map} +1 -1
  11. package/dist/{guest-auth-provider-B78Ugfar.mjs → guest-auth-provider-BBSFawAr.mjs} +2 -2
  12. package/dist/{guest-auth-provider-D9wbogwi.mjs.map → guest-auth-provider-BBSFawAr.mjs.map} +1 -1
  13. package/dist/{guest-auth-provider-Czl48mOP.js → guest-auth-provider-BcR-MQ0g.js} +2 -2
  14. package/dist/{guest-auth-provider-Czl48mOP.js.map → guest-auth-provider-BcR-MQ0g.js.map} +1 -1
  15. package/dist/{guest-auth-provider-BFodq9Rc.js → guest-auth-provider-Di0TRg-z.js} +2 -2
  16. package/dist/{guest-auth-provider-BFodq9Rc.js.map → guest-auth-provider-Di0TRg-z.js.map} +1 -1
  17. package/dist/{index-uu8hKP-p.js → index-C_L497w1.js} +191 -51
  18. package/dist/index-C_L497w1.js.map +1 -0
  19. package/dist/{index-SHt4VZZV.mjs → index-L2R6C5Vl.mjs} +190 -52
  20. package/dist/index-L2R6C5Vl.mjs.map +1 -0
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +3 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +1 -1
  25. package/dist/{index.native-D-8IAEy-.mjs → index.native-BIUs8t7F.mjs} +182 -51
  26. package/dist/index.native-BIUs8t7F.mjs.map +1 -0
  27. package/dist/{index.native-D48QQ0-q.js → index.native-BSheiGj2.js} +183 -50
  28. package/dist/index.native-BSheiGj2.js.map +1 -0
  29. package/dist/index.native.d.ts +2 -2
  30. package/dist/index.native.js +3 -1
  31. package/dist/index.native.js.map +1 -1
  32. package/dist/index.native.mjs +1 -1
  33. package/dist/{injected-evm-wallet-provider-HQMOL_mZ.js → injected-evm-wallet-provider-7XawIUdJ.js} +2 -2
  34. package/dist/{injected-evm-wallet-provider-HQMOL_mZ.js.map → injected-evm-wallet-provider-7XawIUdJ.js.map} +1 -1
  35. package/dist/{injected-evm-wallet-provider-UWvcy-LO.mjs → injected-evm-wallet-provider-DSFDC8fh.mjs} +2 -2
  36. package/dist/{injected-evm-wallet-provider-UWvcy-LO.mjs.map → injected-evm-wallet-provider-DSFDC8fh.mjs.map} +1 -1
  37. package/dist/{injected-evm-wallet-provider-CItxws1D.js → injected-evm-wallet-provider-DgWUaVtt.js} +2 -2
  38. package/dist/{injected-evm-wallet-provider-CItxws1D.js.map → injected-evm-wallet-provider-DgWUaVtt.js.map} +1 -1
  39. package/dist/{injected-evm-wallet-provider-CJsLX16m.mjs → injected-evm-wallet-provider-DnOrVMDX.mjs} +2 -2
  40. package/dist/{injected-evm-wallet-provider-CJsLX16m.mjs.map → injected-evm-wallet-provider-DnOrVMDX.mjs.map} +1 -1
  41. package/dist/{injected-wallet-provider-CmXqreEE.js → injected-wallet-provider-BMDJntMD.js} +2 -2
  42. package/dist/{injected-wallet-provider-CmXqreEE.js.map → injected-wallet-provider-BMDJntMD.js.map} +1 -1
  43. package/dist/{injected-wallet-provider-BihII94U.mjs → injected-wallet-provider-DfJvN3sV.mjs} +2 -2
  44. package/dist/{injected-wallet-provider-BihII94U.mjs.map → injected-wallet-provider-DfJvN3sV.mjs.map} +1 -1
  45. package/dist/{injected-wallet-provider-CNYAynIB.mjs → injected-wallet-provider-ZkPzVj5W.mjs} +2 -2
  46. package/dist/{injected-wallet-provider-CNYAynIB.mjs.map → injected-wallet-provider-ZkPzVj5W.mjs.map} +1 -1
  47. package/dist/{injected-wallet-provider-CVeZgfEt.js → injected-wallet-provider-oCRGuls5.js} +2 -2
  48. package/dist/{injected-wallet-provider-CVeZgfEt.js.map → injected-wallet-provider-oCRGuls5.js.map} +1 -1
  49. package/dist/onramp.d.ts +51 -9
  50. package/package.json +2 -2
  51. package/dist/index-SHt4VZZV.mjs.map +0 -1
  52. package/dist/index-uu8hKP-p.js.map +0 -1
  53. package/dist/index.native-D-8IAEy-.mjs.map +0 -1
  54. package/dist/index.native-D48QQ0-q.js.map +0 -1
@@ -3686,6 +3686,7 @@ const OIDC_NO_WALLET = 'This login has no embedded wallet. Enable `"auth": { "wa
3686
3686
  'then sign from a user gesture.';
3687
3687
  const PKCE_STORE_KEY = 'bounded.oidc.pkce';
3688
3688
  const STORED_AUTH_METHOD_KEY$1 = 'bounded_last_auth_method';
3689
+ const ISSUER_SESSION_KIND_KEY$1 = 'bounded_issuer_session_kind';
3689
3690
  // Active session store (Web on web, RN on React Native) — selected by config,
3690
3691
  // not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
3691
3692
  // session it mints is read back through this same selector everywhere.
@@ -4174,6 +4175,13 @@ async function establishInlineSession(data) {
4174
4175
  getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$1, 'email');
4175
4176
  }
4176
4177
  catch ( /* storage unavailable */_a) { /* storage unavailable */ }
4178
+ // Turnkey-native OTP mints an app session directly. It does not establish a
4179
+ // Better Auth browser cookie on the issuer, so logout must not navigate
4180
+ // through the issuer's cookie-clearing /logout endpoint.
4181
+ try {
4182
+ getPlatform().storage.setItem(ISSUER_SESSION_KIND_KEY$1, 'inline');
4183
+ }
4184
+ catch ( /* storage unavailable */_b) { /* storage unavailable */ }
4177
4185
  setCurrentUser(user);
4178
4186
  setAuthProviderInstance(oidcProviderSingleton);
4179
4187
  return user;
@@ -4220,6 +4228,13 @@ async function exchangeCode(code, verifier, redirectUri) {
4220
4228
  getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$1, 'email');
4221
4229
  }
4222
4230
  catch ( /* storage unavailable */_b) { /* storage unavailable */ }
4231
+ // OIDC login does establish the issuer's Better Auth browser cookie. Record
4232
+ // that fact separately from the provider name so logout clears the cookie
4233
+ // and returns through the registered app origin.
4234
+ try {
4235
+ getPlatform().storage.setItem(ISSUER_SESSION_KIND_KEY$1, 'hosted');
4236
+ }
4237
+ catch ( /* storage unavailable */_c) { /* storage unavailable */ }
4223
4238
  setCurrentUser(user);
4224
4239
  setAuthProviderInstance(oidcProviderSingleton);
4225
4240
  return user;
@@ -4236,6 +4251,7 @@ const objectIdentityKeys = new WeakMap();
4236
4251
  let nextObjectIdentityKey = 1;
4237
4252
  // --- localStorage helpers: track which auth method created the active session ---
4238
4253
  const STORED_AUTH_METHOD_KEY = 'bounded_last_auth_method';
4254
+ const ISSUER_SESSION_KIND_KEY = 'bounded_issuer_session_kind';
4239
4255
  function getStoredAuthMethod() {
4240
4256
  try {
4241
4257
  return getPlatform().storage.getItem(STORED_AUTH_METHOD_KEY);
@@ -4252,11 +4268,25 @@ function setStoredAuthMethod(method) {
4252
4268
  else {
4253
4269
  getPlatform().storage.removeItem(STORED_AUTH_METHOD_KEY);
4254
4270
  }
4271
+ // Non-email providers cannot own a Better Auth browser session. Clear
4272
+ // any prior hosted/inline classification when the active provider is
4273
+ // replaced or the session is removed.
4274
+ if (method !== 'email') {
4275
+ getPlatform().storage.removeItem(ISSUER_SESSION_KIND_KEY);
4276
+ }
4255
4277
  }
4256
4278
  catch (_a) {
4257
4279
  // storage might not be available
4258
4280
  }
4259
4281
  }
4282
+ function getIssuerSessionKind() {
4283
+ try {
4284
+ return getPlatform().storage.getItem(ISSUER_SESSION_KIND_KEY);
4285
+ }
4286
+ catch (_a) {
4287
+ return null;
4288
+ }
4289
+ }
4260
4290
  function getStoredIdToken() {
4261
4291
  try {
4262
4292
  return getActiveSessionManager().getIdToken();
@@ -4515,7 +4545,7 @@ async function getAuthProvider(config) {
4515
4545
  // manager as legacy email sessions. Human login should be started
4516
4546
  // with loginWithRedirect(); this provider is kept as the restore and
4517
4547
  // logout surface for those sessions.
4518
- currentAuthProvider = new (await import('./email-auth-provider-DhAMOILG.mjs')).EmailAuthProvider();
4548
+ currentAuthProvider = new (await import('./email-auth-provider-B3XaJw-G.mjs')).EmailAuthProvider();
4519
4549
  break;
4520
4550
  case "wallet":
4521
4551
  case "phantom": {
@@ -4534,7 +4564,7 @@ async function getAuthProvider(config) {
4534
4564
  if (!isWalletLoginEnabled(config)) {
4535
4565
  throw walletLoginNotEnabledError(authMethod);
4536
4566
  }
4537
- const { InjectedWalletProvider } = await import('./injected-wallet-provider-BihII94U.mjs');
4567
+ const { InjectedWalletProvider } = await import('./injected-wallet-provider-DfJvN3sV.mjs');
4538
4568
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
4539
4569
  break;
4540
4570
  }
@@ -4547,7 +4577,7 @@ async function getAuthProvider(config) {
4547
4577
  // Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
4548
4578
  // to point at a specific wallet or set chainId/domain.
4549
4579
  const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
4550
- const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-UWvcy-LO.mjs');
4580
+ const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-DSFDC8fh.mjs');
4551
4581
  currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
4552
4582
  break;
4553
4583
  }
@@ -4575,7 +4605,7 @@ async function getAuthProvider(config) {
4575
4605
  break;
4576
4606
  case "guest":
4577
4607
  // Anonymous auth: a device-local ed25519 keypair signs the challenge.
4578
- currentAuthProvider = new (await import('./guest-auth-provider-B78Ugfar.mjs')).GuestAuthProvider();
4608
+ currentAuthProvider = new (await import('./guest-auth-provider-BBSFawAr.mjs')).GuestAuthProvider();
4579
4609
  break;
4580
4610
  }
4581
4611
  if (!currentAuthProvider) {
@@ -4631,7 +4661,7 @@ async function signInAnonymously() {
4631
4661
  // Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
4632
4662
  // the main bundle for data-only apps. Specifier is a static string literal
4633
4663
  // so bundle-splitting can see it.
4634
- const provider = new (await import('./guest-auth-provider-B78Ugfar.mjs')).GuestAuthProvider();
4664
+ const provider = new (await import('./guest-auth-provider-BBSFawAr.mjs')).GuestAuthProvider();
4635
4665
  setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
4636
4666
  const commit = (user) => {
4637
4667
  // No await/interleaving inside this block: provider/method/config/marker
@@ -4782,7 +4812,7 @@ async function loginWithWallet(options) {
4782
4812
  ? Object.assign(Object.assign({}, base), { getProvider: options.getProvider }) : base;
4783
4813
  const rpcUrl = normalizeSolanaRpcUrl(cfg.rpcUrl);
4784
4814
  const network = getConfiguredSolanaNetwork(cfg);
4785
- const { InjectedWalletProvider } = await import('./injected-wallet-provider-BihII94U.mjs');
4815
+ const { InjectedWalletProvider } = await import('./injected-wallet-provider-DfJvN3sV.mjs');
4786
4816
  let provider = new InjectedWalletProvider(injectedConfig, rpcUrl, network);
4787
4817
  if (coreConfig.chain === 'offchain')
4788
4818
  provider = new OffchainAuthProvider(provider);
@@ -4854,7 +4884,7 @@ function getCurrentAuthMethod() {
4854
4884
  // allowlist here: on a trusted origin we end the issuer session with a
4855
4885
  // top-level bounce; anywhere else (custom domains, http dev) we keep the
4856
4886
  // local-only logout instead of stranding the user on the issuer origin.
4857
- function issuerLogoutBounceUrl(issuerBase) {
4887
+ function issuerLogoutBounceUrl(issuerBase, idTokenHint) {
4858
4888
  if (typeof window === 'undefined' || typeof document === 'undefined')
4859
4889
  return null; // RN/Node: no shared cookie jar to clear this way
4860
4890
  try {
@@ -4877,12 +4907,27 @@ function issuerLogoutBounceUrl(issuerBase) {
4877
4907
  if (!trusted)
4878
4908
  return null;
4879
4909
  const base = issuerBase.replace(/\/$/, '');
4880
- return `${base}/logout?redirect_uri=${encodeURIComponent(here.href)}`;
4910
+ // #228: send the id_token_hint so the issuer can bind the post-logout redirect to this
4911
+ // session + app + registered origin. Without it the issuer safely falls back to its own
4912
+ // origin (it no longer trusts a bare suffix match), so the return-to-app bounce is a no-op.
4913
+ const hint = idTokenHint ? `&id_token_hint=${encodeURIComponent(idTokenHint)}` : '';
4914
+ return `${base}/logout?redirect_uri=${encodeURIComponent(here.href)}${hint}`;
4881
4915
  }
4882
4916
  catch (_b) {
4883
4917
  return null;
4884
4918
  }
4885
4919
  }
4920
+ // #228 helper, deliberately OUTSIDE logout(): the logout body is source-guarded
4921
+ // against any `catch` so a revokeSession failure can never be swallowed, and a
4922
+ // missing id token is the one benign absence worth tolerating here.
4923
+ function idTokenHintOrNull(sm) {
4924
+ try {
4925
+ return sm.getIdToken();
4926
+ }
4927
+ catch (_a) {
4928
+ return null; /* no id token to hint with */
4929
+ }
4930
+ }
4886
4931
  async function logout$1(options) {
4887
4932
  // Re-initialize the provider if it was cleared by a prior logout — the exact mirror
4888
4933
  // of login()'s self-heal above. A hosted popup/redirect login (loginWithPopup /
@@ -4907,17 +4952,20 @@ async function logout$1(options) {
4907
4952
  const sm = getActiveSessionManager();
4908
4953
  const refreshToken = sm.getRefreshToken();
4909
4954
  const sessionIssuer = sm.getIssuer();
4955
+ // #228: capture the id token NOW, before currentAuthProvider.logout() clears the session, so we
4956
+ // can hand it to the issuer /logout as an id_token_hint (which binds the post-logout redirect).
4957
+ const idTokenHint = idTokenHintOrNull(sm);
4910
4958
  if (refreshToken)
4911
4959
  await revokeSession(refreshToken, sessionIssuer !== null && sessionIssuer !== void 0 ? sessionIssuer : undefined);
4912
- // Hosted sessions ('email' covers the whole hosted surface: email OTP +
4913
- // social + the OIDC redirect/popup flows) are the ones with an issuer
4914
- // cookie; wallet and guest sessions have nothing hosted to end.
4915
- // Check the STORED marker as well as the in-memory method: the hosted
4916
- // OIDC flows (loginWithPopup / completeLoginFromRedirect) write the
4917
- // 'email' marker via exchangeCode() but never touch currentAuthMethod,
4918
- // which stays 'guest'/explicit even after a hosted upgrade. Worst case of
4919
- // a false positive is a harmless idempotent issuer bounce.
4920
- const wasHosted = currentAuthMethod === 'email' || getStoredAuthMethod() === 'email';
4960
+ // Only OIDC-hosted sessions own an issuer browser cookie. Turnkey-native
4961
+ // email OTP is also an email auth method, but it mints the app session
4962
+ // inline and has no issuer cookie to clear. Use the explicit session-kind
4963
+ // marker when present. The auth-method fallback preserves correct logout
4964
+ // behavior for hosted sessions created by older SDK versions.
4965
+ const issuerSessionKind = getIssuerSessionKind();
4966
+ const wasHosted = issuerSessionKind === 'hosted'
4967
+ || (issuerSessionKind === null
4968
+ && (currentAuthMethod === 'email' || getStoredAuthMethod() === 'email'));
4921
4969
  await currentAuthProvider.logout();
4922
4970
  setStoredAuthMethod(null);
4923
4971
  currentAuthProvider = null;
@@ -4927,7 +4975,7 @@ async function logout$1(options) {
4927
4975
  const issuerBase = sessionIssuer
4928
4976
  || (await getConfig()).humanAuthApiUrl
4929
4977
  || 'https://auth.bounded.sh';
4930
- const bounce = issuerLogoutBounceUrl(issuerBase);
4978
+ const bounce = issuerLogoutBounceUrl(issuerBase, idTokenHint);
4931
4979
  // Local state is already cleared, so even if the navigation is stopped
4932
4980
  // (tab close, extension) the app-side logout has fully happened.
4933
4981
  if (bounce)
@@ -7363,30 +7411,117 @@ async function signSolanaMessageViaTurnkey(message) {
7363
7411
  // session is bound server-side to the caller's verified Bounded JWT, and the
7364
7412
  // destination defaults to that user's embedded wallet.
7365
7413
  //
7414
+ // Amount semantics:
7415
+ // amountUsd - exact fiat in; the fee/output breakdown comes back as `quote`.
7416
+ // minAmountOut - a FLOOR on crypto received. The platform quotes Coinbase's
7417
+ // worst-fee payment method (card) and pads by `slippagePad` (default 1%),
7418
+ // so cheaper methods overshoot the floor rather than undershooting it. The
7419
+ // floor holds unless the market moves more than the pad while the user sits
7420
+ // on the checkout screen - which is why completion is VERIFIED: after the
7421
+ // popup closes we poll the platform's status leg and resolve with the
7422
+ // actual filled amount when Coinbase reports it.
7423
+ //
7366
7424
  // Works on Bounded-served origins (<app>.bounded.page / custom domains). On a
7367
7425
  // local dev origin the session endpoint does not exist; we fail with a clear
7368
7426
  // error rather than guessing a host.
7369
7427
  function hasDOM() {
7370
7428
  return typeof window !== 'undefined' && typeof document !== 'undefined';
7371
7429
  }
7430
+ async function authedFetch(path, init) {
7431
+ const idToken = await getIdToken();
7432
+ if (!idToken)
7433
+ throw new Error('onramp: no active session - call login() first');
7434
+ return fetch(`${window.location.origin}${path}`, Object.assign(Object.assign({}, init), { headers: Object.assign(Object.assign({}, init === null || init === void 0 ? void 0 : init.headers), { Authorization: `Bearer ${idToken}` }) }));
7435
+ }
7436
+ async function readError(res) {
7437
+ var _a;
7438
+ try {
7439
+ return (_a = (await res.json()).error) !== null && _a !== void 0 ? _a : '';
7440
+ }
7441
+ catch (_b) {
7442
+ return '';
7443
+ }
7444
+ }
7445
+ function sessionBody(options, extra) {
7446
+ return JSON.stringify(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (options.asset ? { asset: options.asset } : {})), (options.address ? { address: options.address } : {})), (options.amountUsd !== undefined ? { amountUsd: options.amountUsd } : {})), (options.minAmountOut !== undefined ? { minAmountOut: options.minAmountOut } : {})), (options.slippagePad !== undefined ? { slippagePad: options.slippagePad } : {})), extra));
7447
+ }
7448
+ function resolveAddress(options) {
7449
+ var _a, _b;
7450
+ const user = getCurrentUser();
7451
+ const address = (_b = (_a = options.address) !== null && _a !== void 0 ? _a : user === null || user === void 0 ? void 0 : user.address) !== null && _b !== void 0 ? _b : undefined;
7452
+ if (!address) {
7453
+ throw new Error(user
7454
+ ? 'onramp: the signed-in account has no wallet address - pass { address } explicitly'
7455
+ : 'onramp: no signed-in user - call login() first or pass { address }');
7456
+ }
7457
+ return address;
7458
+ }
7372
7459
  /**
7373
- * Open Coinbase Onramp for the signed-in user. Resolves when the popup closes.
7460
+ * Preview a quote (fees + exact/floor output) WITHOUT opening any window or
7461
+ * consuming a session. Show this to the user, then call onramp() to check out.
7462
+ */
7463
+ async function onrampQuote(options = {}) {
7464
+ if (!hasDOM())
7465
+ throw new Error('onrampQuote is web-only');
7466
+ resolveAddress(options);
7467
+ const res = await authedFetch('/__bounded/onramp/session', {
7468
+ method: 'POST',
7469
+ headers: { 'content-type': 'application/json' },
7470
+ body: sessionBody(options, { quoteOnly: true }),
7471
+ });
7472
+ if (!res.ok)
7473
+ throw new Error(`onrampQuote: failed (${res.status}${await readError(res).then((e) => (e ? `: ${e}` : ''))})`);
7474
+ return (await res.json());
7475
+ }
7476
+ /** The signed-in user's recent onramp transactions (newest first). */
7477
+ async function onrampStatus(sinceMs) {
7478
+ if (!hasDOM())
7479
+ throw new Error('onrampStatus is web-only');
7480
+ const res = await authedFetch(`/__bounded/onramp/status${sinceMs ? `?since=${Math.floor(sinceMs)}` : ''}`);
7481
+ if (!res.ok)
7482
+ throw new Error(`onrampStatus: failed (${res.status})`);
7483
+ return (await res.json()).transactions;
7484
+ }
7485
+ function isSuccess(t) {
7486
+ return /SUCCESS|COMPLETED/i.test(t.status);
7487
+ }
7488
+ function isPending(t) {
7489
+ return /PROGRESS|PENDING|CREATED/i.test(t.status);
7490
+ }
7491
+ async function verifyCompletion(openedAt, asset, timeoutMs) {
7492
+ const deadline = Date.now() + timeoutMs;
7493
+ // 60s of clock-skew slack on `since`: Coinbase stamps server-side.
7494
+ const since = openedAt - 60000;
7495
+ for (;;) {
7496
+ let sawPending = false;
7497
+ try {
7498
+ const txs = await onrampStatus(since);
7499
+ const done = txs.find((t) => isSuccess(t) && (t.asset === null || t.asset === asset));
7500
+ if (done)
7501
+ return done;
7502
+ sawPending = txs.some(isPending);
7503
+ }
7504
+ catch ( /* transient - keep polling until the deadline */_a) { /* transient - keep polling until the deadline */ }
7505
+ // No completed fill and nothing in flight: one grace poll cycle still
7506
+ // runs (Coinbase's record can lag the redirect), but don't burn the
7507
+ // full window when the user plainly abandoned checkout.
7508
+ if (Date.now() >= deadline)
7509
+ return null;
7510
+ if (!sawPending && Date.now() >= openedAt + 12000 && Date.now() - openedAt > timeoutMs / 2)
7511
+ return null;
7512
+ await new Promise((r) => setTimeout(r, 3000));
7513
+ }
7514
+ }
7515
+ /**
7516
+ * Open Coinbase Onramp for the signed-in user. Resolves after the window
7517
+ * closes, with completion VERIFIED against Coinbase when possible.
7374
7518
  * Call from a click handler - popup blockers require user activation.
7375
7519
  */
7376
7520
  async function onramp(options = {}) {
7377
- var _a, _b, _c;
7521
+ var _a;
7378
7522
  if (!hasDOM())
7379
7523
  throw new Error('onramp() is web-only');
7380
- const user = getCurrentUser();
7381
- const address = (_b = (_a = options.address) !== null && _a !== void 0 ? _a : user === null || user === void 0 ? void 0 : user.address) !== null && _b !== void 0 ? _b : undefined;
7382
- if (!address) {
7383
- throw new Error(user
7384
- ? 'onramp(): the signed-in account has no wallet address - pass { address } explicitly'
7385
- : 'onramp(): no signed-in user - call login() first or pass { address }');
7386
- }
7387
- const idToken = await getIdToken();
7388
- if (!idToken)
7389
- throw new Error('onramp(): no active session - call login() first');
7524
+ resolveAddress(options);
7390
7525
  // Open the window synchronously so the click's user-activation window is not
7391
7526
  // consumed by the fetch below (same hard-won pattern as loginWithPopup).
7392
7527
  let popup = null;
@@ -7399,21 +7534,17 @@ async function onramp(options = {}) {
7399
7534
  }
7400
7535
  let session;
7401
7536
  try {
7402
- const res = await fetch(`${window.location.origin}/__bounded/onramp/session`, {
7537
+ const res = await authedFetch('/__bounded/onramp/session', {
7403
7538
  method: 'POST',
7404
- headers: { Authorization: `Bearer ${idToken}`, 'content-type': 'application/json' },
7405
- body: JSON.stringify(Object.assign(Object.assign(Object.assign({}, (options.asset ? { asset: options.asset } : {})), (options.address ? { address: options.address } : {})), (options.amountUsd !== undefined ? { amountUsd: options.amountUsd } : {}))),
7539
+ headers: { 'content-type': 'application/json' },
7540
+ body: sessionBody(options),
7406
7541
  });
7407
7542
  if (!res.ok) {
7408
- let detail = '';
7409
- try {
7410
- detail = (_c = (await res.json()).error) !== null && _c !== void 0 ? _c : '';
7411
- }
7412
- catch ( /* keep status only */_d) { /* keep status only */ }
7543
+ const detail = await readError(res);
7413
7544
  if (res.status === 404) {
7414
- throw new Error('onramp(): session endpoint not found - onramp works on Bounded-served origins (your deployed app URL), not local dev servers');
7545
+ throw new Error('onramp: session endpoint not found - onramp works on Bounded-served origins (your deployed app URL), not local dev servers');
7415
7546
  }
7416
- throw new Error(`onramp(): session mint failed (${res.status}${detail ? `: ${detail}` : ''})`);
7547
+ throw new Error(`onramp: session mint failed (${res.status}${detail ? `: ${detail}` : ''})`);
7417
7548
  }
7418
7549
  session = (await res.json());
7419
7550
  }
@@ -7421,40 +7552,47 @@ async function onramp(options = {}) {
7421
7552
  try {
7422
7553
  popup === null || popup === void 0 ? void 0 : popup.close();
7423
7554
  }
7424
- catch ( /* ignore */_e) { /* ignore */ }
7555
+ catch ( /* ignore */_b) { /* ignore */ }
7425
7556
  throw error;
7426
7557
  }
7558
+ const openedAt = Date.now();
7559
+ const base = Object.assign({ address: session.address, asset: session.asset }, (session.quote ? { quote: session.quote } : {}));
7427
7560
  if (popup) {
7428
7561
  try {
7429
7562
  popup.location.replace(session.url);
7430
7563
  }
7431
- catch (_f) {
7564
+ catch (_c) {
7432
7565
  try {
7433
7566
  popup.location.href = session.url;
7434
7567
  }
7435
- catch (_g) {
7568
+ catch (_d) {
7436
7569
  try {
7437
7570
  popup.close();
7438
7571
  }
7439
- catch ( /* ignore */_h) { /* ignore */ }
7440
- throw new Error('onramp(): popup failed to navigate - retry with { newTab: true }');
7572
+ catch ( /* ignore */_e) { /* ignore */ }
7573
+ throw new Error('onramp: popup failed to navigate - retry with { newTab: true }');
7441
7574
  }
7442
7575
  }
7443
- return await new Promise((resolve) => {
7576
+ await new Promise((resolve) => {
7444
7577
  const timer = setInterval(() => {
7445
7578
  if (popup.closed) {
7446
7579
  clearInterval(timer);
7447
- resolve({ status: 'closed', address: session.address, asset: session.asset });
7580
+ resolve();
7448
7581
  }
7449
7582
  }, 500);
7450
7583
  });
7584
+ const fill = await verifyCompletion(openedAt, session.asset, (_a = options.completionTimeoutMs) !== null && _a !== void 0 ? _a : 24000);
7585
+ if (fill) {
7586
+ return Object.assign(Object.assign(Object.assign(Object.assign({}, base), { status: 'completed' }), (fill.amountOut !== null ? { actualOut: fill.amountOut } : {})), (fill.txHash ? { txHash: fill.txHash } : {}));
7587
+ }
7588
+ return Object.assign(Object.assign({}, base), { status: 'closed' });
7451
7589
  }
7452
7590
  // New-tab path (popup was blocked or explicitly requested): cross-origin tab
7453
- // close is not observable, so resolve immediately.
7591
+ // close is not observable, so resolve immediately; verify via onrampStatus().
7454
7592
  const tab = window.open(session.url, '_blank');
7455
7593
  if (!tab)
7456
- throw new Error('onramp(): window.open was blocked - call onramp() from a click handler');
7457
- return { status: 'opened', address: session.address, asset: session.asset };
7594
+ throw new Error('onramp: window.open was blocked - call onramp() from a click handler');
7595
+ return Object.assign(Object.assign({}, base), { status: 'opened' });
7458
7596
  }
7459
7597
 
7460
7598
  /**
@@ -7925,5 +8063,5 @@ function defaultInjectedEvmProvider() {
7925
8063
  return null;
7926
8064
  }
7927
8065
 
7928
- export { BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as $, completeLoginFromRedirect as A, loginWithPopup as B, completeLoginInPopup as C, openBoundedWidget as D, EMBEDDED_WALLET_UNSUPPORTED as E, useAuth as F, useQuery as G, useBuildRun as H, apps as I, deserializeTransaction as J, signSolanaMessageViaTurnkey as K, getOrCreateTurnkeyWallet as L, startTurnkeyEmailLogin as M, getIdToken as N, onramp as O, setPlatform as P, OffchainAuthProvider as Q, PrivyExpoProvider as R, requestEip6963Providers as S, builds as T, BuildsApiError as U, NON_TERMINAL_STATES as V, TERMINAL_STATES as W, RUN_STATES as X, PARK_REASONS as Y, FAILURE_REASONS as Z, BUILD_REVIEW_CAPABILITY_SCHEMA as _, hasEmbeddedEvmWallet as a, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as a0, embeddedSignAndSubmit as b, setAuthLoading as c, defaultInjectedProvider as d, embeddedRunEvmTransaction as e, bufferExports as f, getPlatform as g, hasEmbeddedWallet as h, bs58 as i, confirmAndCheckTransaction as j, defaultInjectedEvmProvider as k, init as l, getCurrentUser as m, normalizeSolanaRpcUrl as n, onAuthStateChanged as o, onAuthLoadingChanged as p, getAuthLoading as q, resolveSolanaRpcUrl as r, setCurrentUser as s, login as t, logout as u, getAuthProvider as v, signInAnonymously as w, loginWithPrivy as x, loginWithWallet as y, loginWithRedirect as z };
7929
- //# sourceMappingURL=index-SHt4VZZV.mjs.map
8066
+ export { FAILURE_REASONS as $, completeLoginFromRedirect as A, loginWithPopup as B, completeLoginInPopup as C, openBoundedWidget as D, EMBEDDED_WALLET_UNSUPPORTED as E, useAuth as F, useQuery as G, useBuildRun as H, apps as I, deserializeTransaction as J, signSolanaMessageViaTurnkey as K, getOrCreateTurnkeyWallet as L, startTurnkeyEmailLogin as M, getIdToken as N, onramp as O, onrampQuote as P, onrampStatus as Q, setPlatform as R, OffchainAuthProvider as S, PrivyExpoProvider as T, requestEip6963Providers as U, builds as V, BuildsApiError as W, NON_TERMINAL_STATES as X, TERMINAL_STATES as Y, RUN_STATES as Z, PARK_REASONS as _, hasEmbeddedEvmWallet as a, BUILD_REVIEW_CAPABILITY_SCHEMA as a0, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as a1, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as a2, embeddedSignAndSubmit as b, setAuthLoading as c, defaultInjectedProvider as d, embeddedRunEvmTransaction as e, bufferExports as f, getPlatform as g, hasEmbeddedWallet as h, bs58 as i, confirmAndCheckTransaction as j, defaultInjectedEvmProvider as k, init as l, getCurrentUser as m, normalizeSolanaRpcUrl as n, onAuthStateChanged as o, onAuthLoadingChanged as p, getAuthLoading as q, resolveSolanaRpcUrl as r, setCurrentUser as s, login as t, logout as u, getAuthProvider as v, signInAnonymously as w, loginWithPrivy as x, loginWithWallet as y, loginWithRedirect as z };
8067
+ //# sourceMappingURL=index-L2R6C5Vl.mjs.map