@bounded-sh/client 0.0.59 → 0.0.60

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-B5yIZe1l.js} +2 -2
  2. package/dist/{email-auth-provider-N-dfsNnE.js.map → email-auth-provider-B5yIZe1l.js.map} +1 -1
  3. package/dist/{email-auth-provider-BSw9FRjg.js → email-auth-provider-BCEdcnRu.js} +2 -2
  4. package/dist/{email-auth-provider-BSw9FRjg.js.map → email-auth-provider-BCEdcnRu.js.map} +1 -1
  5. package/dist/{email-auth-provider-deB_ADc5.mjs → email-auth-provider-BF-vvbAt.mjs} +2 -2
  6. package/dist/{email-auth-provider-deB_ADc5.mjs.map → email-auth-provider-BF-vvbAt.mjs.map} +1 -1
  7. package/dist/{email-auth-provider-DhAMOILG.mjs → email-auth-provider-nBv7mW9q.mjs} +2 -2
  8. package/dist/{email-auth-provider-DhAMOILG.mjs.map → email-auth-provider-nBv7mW9q.mjs.map} +1 -1
  9. package/dist/{guest-auth-provider-BFodq9Rc.js → guest-auth-provider-CZ-CpNhD.js} +2 -2
  10. package/dist/{guest-auth-provider-BFodq9Rc.js.map → guest-auth-provider-CZ-CpNhD.js.map} +1 -1
  11. package/dist/{guest-auth-provider-Czl48mOP.js → guest-auth-provider-CrRreOyf.js} +2 -2
  12. package/dist/{guest-auth-provider-Czl48mOP.js.map → guest-auth-provider-CrRreOyf.js.map} +1 -1
  13. package/dist/{guest-auth-provider-D9wbogwi.mjs → guest-auth-provider-DFNAHmtY.mjs} +2 -2
  14. package/dist/{guest-auth-provider-B78Ugfar.mjs.map → guest-auth-provider-DFNAHmtY.mjs.map} +1 -1
  15. package/dist/{guest-auth-provider-B78Ugfar.mjs → guest-auth-provider-DGvQgIwm.mjs} +2 -2
  16. package/dist/{guest-auth-provider-D9wbogwi.mjs.map → guest-auth-provider-DGvQgIwm.mjs.map} +1 -1
  17. package/dist/{index-SHt4VZZV.mjs → index-CQtGkKVd.mjs} +151 -43
  18. package/dist/index-CQtGkKVd.mjs.map +1 -0
  19. package/dist/{index-uu8hKP-p.js → index-iu8vB4ye.js} +152 -42
  20. package/dist/index-iu8vB4ye.js.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-D48QQ0-q.js → index.native-CDfx0N6p.js} +151 -41
  26. package/dist/index.native-CDfx0N6p.js.map +1 -0
  27. package/dist/{index.native-D-8IAEy-.mjs → index.native-Crfn_NOB.mjs} +150 -42
  28. package/dist/index.native-Crfn_NOB.mjs.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-CJsLX16m.mjs → injected-evm-wallet-provider-Bi9G3X35.mjs} +2 -2
  34. package/dist/{injected-evm-wallet-provider-CJsLX16m.mjs.map → injected-evm-wallet-provider-Bi9G3X35.mjs.map} +1 -1
  35. package/dist/{injected-evm-wallet-provider-HQMOL_mZ.js → injected-evm-wallet-provider-CGa692L1.js} +2 -2
  36. package/dist/{injected-evm-wallet-provider-HQMOL_mZ.js.map → injected-evm-wallet-provider-CGa692L1.js.map} +1 -1
  37. package/dist/{injected-evm-wallet-provider-UWvcy-LO.mjs → injected-evm-wallet-provider-D8lYGcM2.mjs} +2 -2
  38. package/dist/{injected-evm-wallet-provider-UWvcy-LO.mjs.map → injected-evm-wallet-provider-D8lYGcM2.mjs.map} +1 -1
  39. package/dist/{injected-evm-wallet-provider-CItxws1D.js → injected-evm-wallet-provider-LTWiACl5.js} +2 -2
  40. package/dist/{injected-evm-wallet-provider-CItxws1D.js.map → injected-evm-wallet-provider-LTWiACl5.js.map} +1 -1
  41. package/dist/{injected-wallet-provider-CNYAynIB.mjs → injected-wallet-provider-CcyA5JBS.mjs} +2 -2
  42. package/dist/{injected-wallet-provider-CNYAynIB.mjs.map → injected-wallet-provider-CcyA5JBS.mjs.map} +1 -1
  43. package/dist/{injected-wallet-provider-CVeZgfEt.js → injected-wallet-provider-DYZFxIdd.js} +2 -2
  44. package/dist/{injected-wallet-provider-CVeZgfEt.js.map → injected-wallet-provider-DYZFxIdd.js.map} +1 -1
  45. package/dist/{injected-wallet-provider-CmXqreEE.js → injected-wallet-provider-TPx8Wzvq.js} +2 -2
  46. package/dist/{injected-wallet-provider-CmXqreEE.js.map → injected-wallet-provider-TPx8Wzvq.js.map} +1 -1
  47. package/dist/{injected-wallet-provider-BihII94U.mjs → injected-wallet-provider-kx8tcMKM.mjs} +2 -2
  48. package/dist/{injected-wallet-provider-BihII94U.mjs.map → injected-wallet-provider-kx8tcMKM.mjs.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
package/dist/index.d.ts CHANGED
@@ -30,8 +30,8 @@ export type { TurnkeySignResult, TurnkeyWallet } from "./auth/turnkey-signer-bri
30
30
  export { startTurnkeyEmailLogin } from "./auth/turnkey-auth";
31
31
  export type { TurnkeyEmailLoginHandle } from "./auth/turnkey-auth";
32
32
  export { getIdToken } from "./utils";
33
- export { onramp } from "./onramp";
34
- export type { OnrampOptions, OnrampResult } from "./onramp";
33
+ export { onramp, onrampQuote, onrampStatus } from "./onramp";
34
+ export type { OnrampOptions, OnrampResult, OnrampQuoteInfo, OnrampTransaction } from "./onramp";
35
35
  export { setPlatform, getPlatform } from "./platform";
36
36
  export type { PlatformAdapter, StorageAdapter } from "./platform";
37
37
  export { OffchainAuthProvider, OffchainAuthProviderConfig, } from "./auth/providers/offchain-auth-provider";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-uu8hKP-p.js');
3
+ var index = require('./index-iu8vB4ye.js');
4
4
  var core = require('@bounded-sh/core');
5
5
  require('react');
6
6
 
@@ -40,6 +40,8 @@ exports.logout = index.logout;
40
40
  exports.onAuthLoadingChanged = index.onAuthLoadingChanged;
41
41
  exports.onAuthStateChanged = index.onAuthStateChanged;
42
42
  exports.onramp = index.onramp;
43
+ exports.onrampQuote = index.onrampQuote;
44
+ exports.onrampStatus = index.onrampStatus;
43
45
  exports.openBoundedWidget = index.openBoundedWidget;
44
46
  exports.requestEip6963Providers = index.requestEip6963Providers;
45
47
  exports.setPlatform = index.setPlatform;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { a0 as BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA, $ as BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA, _ as BUILD_REVIEW_CAPABILITY_SCHEMA, U as BuildsApiError, Z as FAILURE_REASONS, V as NON_TERMINAL_STATES, Q as OffchainAuthProvider, Y as PARK_REASONS, R as PrivyExpoProvider, X as RUN_STATES, W as TERMINAL_STATES, I as apps, T as builds, A as completeLoginFromRedirect, C as completeLoginInPopup, k as defaultInjectedEvmProvider, d as defaultInjectedProvider, J as deserializeTransaction, q as getAuthLoading, v as getAuthProvider, m as getCurrentUser, N as getIdToken, L as getOrCreateTurnkeyWallet, g as getPlatform, l as init, t as login, B as loginWithPopup, x as loginWithPrivy, z as loginWithRedirect, y as loginWithWallet, u as logout, p as onAuthLoadingChanged, o as onAuthStateChanged, O as onramp, D as openBoundedWidget, S as requestEip6963Providers, P as setPlatform, w as signInAnonymously, K as signSolanaMessageViaTurnkey, M as startTurnkeyEmailLogin, F as useAuth, H as useBuildRun, G as useQuery } from './index-SHt4VZZV.mjs';
1
+ export { a2 as BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA, a1 as BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA, a0 as BUILD_REVIEW_CAPABILITY_SCHEMA, W as BuildsApiError, $ as FAILURE_REASONS, X as NON_TERMINAL_STATES, S as OffchainAuthProvider, _ as PARK_REASONS, T as PrivyExpoProvider, Z as RUN_STATES, Y as TERMINAL_STATES, I as apps, V as builds, A as completeLoginFromRedirect, C as completeLoginInPopup, k as defaultInjectedEvmProvider, d as defaultInjectedProvider, J as deserializeTransaction, q as getAuthLoading, v as getAuthProvider, m as getCurrentUser, N as getIdToken, L as getOrCreateTurnkeyWallet, g as getPlatform, l as init, t as login, B as loginWithPopup, x as loginWithPrivy, z as loginWithRedirect, y as loginWithWallet, u as logout, p as onAuthLoadingChanged, o as onAuthStateChanged, O as onramp, P as onrampQuote, Q as onrampStatus, D as openBoundedWidget, U as requestEip6963Providers, R as setPlatform, w as signInAnonymously, K as signSolanaMessageViaTurnkey, M as startTurnkeyEmailLogin, F as useAuth, H as useBuildRun, G as useQuery } from './index-CQtGkKVd.mjs';
2
2
  export * from '@bounded-sh/core';
3
3
  export { BoundedDeclineError, DECLINE_EVENT_NAME, RATE_LIMIT_EVENT_NAME, ScopedClientReadOnlyError, aggregate, count, createClient, deriveDecline, dispatchDeclineEvent, dispatchRateLimitEvent, docId, get, getConfig, getFiles, getMany, queryAggregate, runExpression, runExpressionMany, runQuery, runQueryMany, search, set, setFile, setMany, signAndSubmitTransaction, signMessage, signTransaction, subscribe } from '@bounded-sh/core';
4
4
  import 'react';
@@ -4474,7 +4474,7 @@ async function getAuthProvider(config) {
4474
4474
  // manager as legacy email sessions. Human login should be started
4475
4475
  // with loginWithRedirect(); this provider is kept as the restore and
4476
4476
  // logout surface for those sessions.
4477
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-BSw9FRjg.js'); })).EmailAuthProvider();
4477
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-BCEdcnRu.js'); })).EmailAuthProvider();
4478
4478
  break;
4479
4479
  case "wallet":
4480
4480
  case "phantom": {
@@ -4493,7 +4493,7 @@ async function getAuthProvider(config) {
4493
4493
  if (!isWalletLoginEnabled(config)) {
4494
4494
  throw walletLoginNotEnabledError(authMethod);
4495
4495
  }
4496
- const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-CmXqreEE.js'); });
4496
+ const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-TPx8Wzvq.js'); });
4497
4497
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
4498
4498
  break;
4499
4499
  }
@@ -4506,7 +4506,7 @@ async function getAuthProvider(config) {
4506
4506
  // Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
4507
4507
  // to point at a specific wallet or set chainId/domain.
4508
4508
  const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
4509
- const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-HQMOL_mZ.js'); });
4509
+ const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-CGa692L1.js'); });
4510
4510
  currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
4511
4511
  break;
4512
4512
  }
@@ -4534,7 +4534,7 @@ async function getAuthProvider(config) {
4534
4534
  break;
4535
4535
  case "guest":
4536
4536
  // Anonymous auth: a device-local ed25519 keypair signs the challenge.
4537
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-Czl48mOP.js'); })).GuestAuthProvider();
4537
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-CrRreOyf.js'); })).GuestAuthProvider();
4538
4538
  break;
4539
4539
  }
4540
4540
  if (!currentAuthProvider) {
@@ -4590,7 +4590,7 @@ async function signInAnonymously() {
4590
4590
  // Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
4591
4591
  // the main bundle for data-only apps. Specifier is a static string literal
4592
4592
  // so bundle-splitting can see it.
4593
- const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-Czl48mOP.js'); })).GuestAuthProvider();
4593
+ const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-CrRreOyf.js'); })).GuestAuthProvider();
4594
4594
  setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
4595
4595
  const commit = (user) => {
4596
4596
  // No await/interleaving inside this block: provider/method/config/marker
@@ -4775,7 +4775,7 @@ function getCurrentAuthMethod() {
4775
4775
  // allowlist here: on a trusted origin we end the issuer session with a
4776
4776
  // top-level bounce; anywhere else (custom domains, http dev) we keep the
4777
4777
  // local-only logout instead of stranding the user on the issuer origin.
4778
- function issuerLogoutBounceUrl(issuerBase) {
4778
+ function issuerLogoutBounceUrl(issuerBase, idTokenHint) {
4779
4779
  if (typeof window === 'undefined' || typeof document === 'undefined')
4780
4780
  return null; // RN/Node: no shared cookie jar to clear this way
4781
4781
  try {
@@ -4798,12 +4798,27 @@ function issuerLogoutBounceUrl(issuerBase) {
4798
4798
  if (!trusted)
4799
4799
  return null;
4800
4800
  const base = issuerBase.replace(/\/$/, '');
4801
- return `${base}/logout?redirect_uri=${encodeURIComponent(here.href)}`;
4801
+ // #228: send the id_token_hint so the issuer can bind the post-logout redirect to this
4802
+ // session + app + registered origin. Without it the issuer safely falls back to its own
4803
+ // origin (it no longer trusts a bare suffix match), so the return-to-app bounce is a no-op.
4804
+ const hint = idTokenHint ? `&id_token_hint=${encodeURIComponent(idTokenHint)}` : '';
4805
+ return `${base}/logout?redirect_uri=${encodeURIComponent(here.href)}${hint}`;
4802
4806
  }
4803
4807
  catch (_b) {
4804
4808
  return null;
4805
4809
  }
4806
4810
  }
4811
+ // #228 helper, deliberately OUTSIDE logout(): the logout body is source-guarded
4812
+ // against any `catch` so a revokeSession failure can never be swallowed, and a
4813
+ // missing id token is the one benign absence worth tolerating here.
4814
+ function idTokenHintOrNull(sm) {
4815
+ try {
4816
+ return sm.getIdToken();
4817
+ }
4818
+ catch (_a) {
4819
+ return null; /* no id token to hint with */
4820
+ }
4821
+ }
4807
4822
  async function logout$1(options) {
4808
4823
  // Re-initialize the provider if it was cleared by a prior logout — the exact mirror
4809
4824
  // of login()'s self-heal above. A hosted popup/redirect login (loginWithPopup /
@@ -4828,6 +4843,9 @@ async function logout$1(options) {
4828
4843
  const sm = core.getActiveSessionManager();
4829
4844
  const refreshToken = sm.getRefreshToken();
4830
4845
  const sessionIssuer = sm.getIssuer();
4846
+ // #228: capture the id token NOW, before currentAuthProvider.logout() clears the session, so we
4847
+ // can hand it to the issuer /logout as an id_token_hint (which binds the post-logout redirect).
4848
+ const idTokenHint = idTokenHintOrNull(sm);
4831
4849
  if (refreshToken)
4832
4850
  await core.revokeSession(refreshToken, sessionIssuer !== null && sessionIssuer !== void 0 ? sessionIssuer : undefined);
4833
4851
  // Hosted sessions ('email' covers the whole hosted surface: email OTP +
@@ -4848,7 +4866,7 @@ async function logout$1(options) {
4848
4866
  const issuerBase = sessionIssuer
4849
4867
  || (await core.getConfig()).humanAuthApiUrl
4850
4868
  || 'https://auth.bounded.sh';
4851
- const bounce = issuerLogoutBounceUrl(issuerBase);
4869
+ const bounce = issuerLogoutBounceUrl(issuerBase, idTokenHint);
4852
4870
  // Local state is already cleared, so even if the navigation is stopped
4853
4871
  // (tab close, extension) the app-side logout has fully happened.
4854
4872
  if (bounce)
@@ -5831,30 +5849,117 @@ async function deserializeTransaction(tx) {
5831
5849
  // session is bound server-side to the caller's verified Bounded JWT, and the
5832
5850
  // destination defaults to that user's embedded wallet.
5833
5851
  //
5852
+ // Amount semantics:
5853
+ // amountUsd - exact fiat in; the fee/output breakdown comes back as `quote`.
5854
+ // minAmountOut - a FLOOR on crypto received. The platform quotes Coinbase's
5855
+ // worst-fee payment method (card) and pads by `slippagePad` (default 1%),
5856
+ // so cheaper methods overshoot the floor rather than undershooting it. The
5857
+ // floor holds unless the market moves more than the pad while the user sits
5858
+ // on the checkout screen - which is why completion is VERIFIED: after the
5859
+ // popup closes we poll the platform's status leg and resolve with the
5860
+ // actual filled amount when Coinbase reports it.
5861
+ //
5834
5862
  // Works on Bounded-served origins (<app>.bounded.page / custom domains). On a
5835
5863
  // local dev origin the session endpoint does not exist; we fail with a clear
5836
5864
  // error rather than guessing a host.
5837
5865
  function hasDOM() {
5838
5866
  return typeof window !== 'undefined' && typeof document !== 'undefined';
5839
5867
  }
5868
+ async function authedFetch(path, init) {
5869
+ const idToken = await getIdToken();
5870
+ if (!idToken)
5871
+ throw new Error('onramp: no active session - call login() first');
5872
+ 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}` }) }));
5873
+ }
5874
+ async function readError(res) {
5875
+ var _a;
5876
+ try {
5877
+ return (_a = (await res.json()).error) !== null && _a !== void 0 ? _a : '';
5878
+ }
5879
+ catch (_b) {
5880
+ return '';
5881
+ }
5882
+ }
5883
+ function sessionBody(options, extra) {
5884
+ 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));
5885
+ }
5886
+ function resolveAddress(options) {
5887
+ var _a, _b;
5888
+ const user = getCurrentUser();
5889
+ 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;
5890
+ if (!address) {
5891
+ throw new Error(user
5892
+ ? 'onramp: the signed-in account has no wallet address - pass { address } explicitly'
5893
+ : 'onramp: no signed-in user - call login() first or pass { address }');
5894
+ }
5895
+ return address;
5896
+ }
5897
+ /**
5898
+ * Preview a quote (fees + exact/floor output) WITHOUT opening any window or
5899
+ * consuming a session. Show this to the user, then call onramp() to check out.
5900
+ */
5901
+ async function onrampQuote(options = {}) {
5902
+ if (!hasDOM())
5903
+ throw new Error('onrampQuote is web-only');
5904
+ resolveAddress(options);
5905
+ const res = await authedFetch('/__bounded/onramp/session', {
5906
+ method: 'POST',
5907
+ headers: { 'content-type': 'application/json' },
5908
+ body: sessionBody(options, { quoteOnly: true }),
5909
+ });
5910
+ if (!res.ok)
5911
+ throw new Error(`onrampQuote: failed (${res.status}${await readError(res).then((e) => (e ? `: ${e}` : ''))})`);
5912
+ return (await res.json());
5913
+ }
5914
+ /** The signed-in user's recent onramp transactions (newest first). */
5915
+ async function onrampStatus(sinceMs) {
5916
+ if (!hasDOM())
5917
+ throw new Error('onrampStatus is web-only');
5918
+ const res = await authedFetch(`/__bounded/onramp/status${sinceMs ? `?since=${Math.floor(sinceMs)}` : ''}`);
5919
+ if (!res.ok)
5920
+ throw new Error(`onrampStatus: failed (${res.status})`);
5921
+ return (await res.json()).transactions;
5922
+ }
5923
+ function isSuccess(t) {
5924
+ return /SUCCESS|COMPLETED/i.test(t.status);
5925
+ }
5926
+ function isPending(t) {
5927
+ return /PROGRESS|PENDING|CREATED/i.test(t.status);
5928
+ }
5929
+ async function verifyCompletion(openedAt, asset, timeoutMs) {
5930
+ const deadline = Date.now() + timeoutMs;
5931
+ // 60s of clock-skew slack on `since`: Coinbase stamps server-side.
5932
+ const since = openedAt - 60000;
5933
+ for (;;) {
5934
+ let sawPending = false;
5935
+ try {
5936
+ const txs = await onrampStatus(since);
5937
+ const done = txs.find((t) => isSuccess(t) && (t.asset === null || t.asset === asset));
5938
+ if (done)
5939
+ return done;
5940
+ sawPending = txs.some(isPending);
5941
+ }
5942
+ catch ( /* transient - keep polling until the deadline */_a) { /* transient - keep polling until the deadline */ }
5943
+ // No completed fill and nothing in flight: one grace poll cycle still
5944
+ // runs (Coinbase's record can lag the redirect), but don't burn the
5945
+ // full window when the user plainly abandoned checkout.
5946
+ if (Date.now() >= deadline)
5947
+ return null;
5948
+ if (!sawPending && Date.now() >= openedAt + 12000 && Date.now() - openedAt > timeoutMs / 2)
5949
+ return null;
5950
+ await new Promise((r) => setTimeout(r, 3000));
5951
+ }
5952
+ }
5840
5953
  /**
5841
- * Open Coinbase Onramp for the signed-in user. Resolves when the popup closes.
5954
+ * Open Coinbase Onramp for the signed-in user. Resolves after the window
5955
+ * closes, with completion VERIFIED against Coinbase when possible.
5842
5956
  * Call from a click handler - popup blockers require user activation.
5843
5957
  */
5844
5958
  async function onramp(options = {}) {
5845
- var _a, _b, _c;
5959
+ var _a;
5846
5960
  if (!hasDOM())
5847
5961
  throw new Error('onramp() is web-only');
5848
- const user = getCurrentUser();
5849
- 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;
5850
- if (!address) {
5851
- throw new Error(user
5852
- ? 'onramp(): the signed-in account has no wallet address - pass { address } explicitly'
5853
- : 'onramp(): no signed-in user - call login() first or pass { address }');
5854
- }
5855
- const idToken = await getIdToken();
5856
- if (!idToken)
5857
- throw new Error('onramp(): no active session - call login() first');
5962
+ resolveAddress(options);
5858
5963
  // Open the window synchronously so the click's user-activation window is not
5859
5964
  // consumed by the fetch below (same hard-won pattern as loginWithPopup).
5860
5965
  let popup = null;
@@ -5867,21 +5972,17 @@ async function onramp(options = {}) {
5867
5972
  }
5868
5973
  let session;
5869
5974
  try {
5870
- const res = await fetch(`${window.location.origin}/__bounded/onramp/session`, {
5975
+ const res = await authedFetch('/__bounded/onramp/session', {
5871
5976
  method: 'POST',
5872
- headers: { Authorization: `Bearer ${idToken}`, 'content-type': 'application/json' },
5873
- 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 } : {}))),
5977
+ headers: { 'content-type': 'application/json' },
5978
+ body: sessionBody(options),
5874
5979
  });
5875
5980
  if (!res.ok) {
5876
- let detail = '';
5877
- try {
5878
- detail = (_c = (await res.json()).error) !== null && _c !== void 0 ? _c : '';
5879
- }
5880
- catch ( /* keep status only */_d) { /* keep status only */ }
5981
+ const detail = await readError(res);
5881
5982
  if (res.status === 404) {
5882
- throw new Error('onramp(): session endpoint not found - onramp works on Bounded-served origins (your deployed app URL), not local dev servers');
5983
+ throw new Error('onramp: session endpoint not found - onramp works on Bounded-served origins (your deployed app URL), not local dev servers');
5883
5984
  }
5884
- throw new Error(`onramp(): session mint failed (${res.status}${detail ? `: ${detail}` : ''})`);
5985
+ throw new Error(`onramp: session mint failed (${res.status}${detail ? `: ${detail}` : ''})`);
5885
5986
  }
5886
5987
  session = (await res.json());
5887
5988
  }
@@ -5889,40 +5990,47 @@ async function onramp(options = {}) {
5889
5990
  try {
5890
5991
  popup === null || popup === void 0 ? void 0 : popup.close();
5891
5992
  }
5892
- catch ( /* ignore */_e) { /* ignore */ }
5993
+ catch ( /* ignore */_b) { /* ignore */ }
5893
5994
  throw error;
5894
5995
  }
5996
+ const openedAt = Date.now();
5997
+ const base = Object.assign({ address: session.address, asset: session.asset }, (session.quote ? { quote: session.quote } : {}));
5895
5998
  if (popup) {
5896
5999
  try {
5897
6000
  popup.location.replace(session.url);
5898
6001
  }
5899
- catch (_f) {
6002
+ catch (_c) {
5900
6003
  try {
5901
6004
  popup.location.href = session.url;
5902
6005
  }
5903
- catch (_g) {
6006
+ catch (_d) {
5904
6007
  try {
5905
6008
  popup.close();
5906
6009
  }
5907
- catch ( /* ignore */_h) { /* ignore */ }
5908
- throw new Error('onramp(): popup failed to navigate - retry with { newTab: true }');
6010
+ catch ( /* ignore */_e) { /* ignore */ }
6011
+ throw new Error('onramp: popup failed to navigate - retry with { newTab: true }');
5909
6012
  }
5910
6013
  }
5911
- return await new Promise((resolve) => {
6014
+ await new Promise((resolve) => {
5912
6015
  const timer = setInterval(() => {
5913
6016
  if (popup.closed) {
5914
6017
  clearInterval(timer);
5915
- resolve({ status: 'closed', address: session.address, asset: session.asset });
6018
+ resolve();
5916
6019
  }
5917
6020
  }, 500);
5918
6021
  });
6022
+ const fill = await verifyCompletion(openedAt, session.asset, (_a = options.completionTimeoutMs) !== null && _a !== void 0 ? _a : 24000);
6023
+ if (fill) {
6024
+ return Object.assign(Object.assign(Object.assign(Object.assign({}, base), { status: 'completed' }), (fill.amountOut !== null ? { actualOut: fill.amountOut } : {})), (fill.txHash ? { txHash: fill.txHash } : {}));
6025
+ }
6026
+ return Object.assign(Object.assign({}, base), { status: 'closed' });
5919
6027
  }
5920
6028
  // New-tab path (popup was blocked or explicitly requested): cross-origin tab
5921
- // close is not observable, so resolve immediately.
6029
+ // close is not observable, so resolve immediately; verify via onrampStatus().
5922
6030
  const tab = window.open(session.url, '_blank');
5923
6031
  if (!tab)
5924
- throw new Error('onramp(): window.open was blocked - call onramp() from a click handler');
5925
- return { status: 'opened', address: session.address, asset: session.asset };
6032
+ throw new Error('onramp: window.open was blocked - call onramp() from a click handler');
6033
+ return Object.assign(Object.assign({}, base), { status: 'opened' });
5926
6034
  }
5927
6035
 
5928
6036
  /**
@@ -6347,6 +6455,8 @@ exports.normalizeSolanaRpcUrl = normalizeSolanaRpcUrl;
6347
6455
  exports.onAuthLoadingChanged = onAuthLoadingChanged;
6348
6456
  exports.onAuthStateChanged = onAuthStateChanged;
6349
6457
  exports.onramp = onramp;
6458
+ exports.onrampQuote = onrampQuote;
6459
+ exports.onrampStatus = onrampStatus;
6350
6460
  exports.resolveSolanaRpcUrl = resolveSolanaRpcUrl;
6351
6461
  exports.setAuthLoading = setAuthLoading;
6352
6462
  exports.setCurrentUser = setCurrentUser;
@@ -6355,4 +6465,4 @@ exports.signInAnonymously = signInAnonymously;
6355
6465
  exports.useAuth = useAuth;
6356
6466
  exports.useBuildRun = useBuildRun;
6357
6467
  exports.useQuery = useQuery;
6358
- //# sourceMappingURL=index.native-D48QQ0-q.js.map
6468
+ //# sourceMappingURL=index.native-CDfx0N6p.js.map