@bounded-sh/client 0.0.62 → 0.0.65

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 (72) hide show
  1. package/dist/auth/providers/email-auth-provider.d.ts +4 -4
  2. package/dist/auth/providers/injected-wallet-provider.d.ts +1 -1
  3. package/dist/auth/providers/privy-expo-provider.d.ts +1 -1
  4. package/dist/auth/turnkey-signer-bridge.d.ts +5 -0
  5. package/dist/auth/turnkey-signing-capability.d.ts +20 -0
  6. package/dist/{email-auth-provider-vWv0rnbd.mjs → email-auth-provider-BLHNLu5l.mjs} +27 -30
  7. package/dist/email-auth-provider-BLHNLu5l.mjs.map +1 -0
  8. package/dist/{email-auth-provider-BxV1zZA_.js → email-auth-provider-CJVJsWXv.js} +27 -30
  9. package/dist/email-auth-provider-CJVJsWXv.js.map +1 -0
  10. package/dist/{email-auth-provider-BttT2sZK.js → email-auth-provider-DYtoSrdm.js} +27 -30
  11. package/dist/email-auth-provider-DYtoSrdm.js.map +1 -0
  12. package/dist/{email-auth-provider-Cb4VKA1E.mjs → email-auth-provider-wqNimboO.mjs} +27 -30
  13. package/dist/email-auth-provider-wqNimboO.mjs.map +1 -0
  14. package/dist/{guest-auth-provider-BBPpydtM.mjs → guest-auth-provider-C3EGlSSp.mjs} +2 -2
  15. package/dist/{guest-auth-provider-CeIZw5x6.mjs.map → guest-auth-provider-C3EGlSSp.mjs.map} +1 -1
  16. package/dist/{guest-auth-provider-CeIZw5x6.mjs → guest-auth-provider-CKkNZ-1p.mjs} +2 -2
  17. package/dist/{guest-auth-provider-BBPpydtM.mjs.map → guest-auth-provider-CKkNZ-1p.mjs.map} +1 -1
  18. package/dist/{guest-auth-provider-BcR-MQ0g.js → guest-auth-provider-CoI0r92a.js} +2 -2
  19. package/dist/{guest-auth-provider-BcR-MQ0g.js.map → guest-auth-provider-CoI0r92a.js.map} +1 -1
  20. package/dist/{guest-auth-provider-peD5Zasl.js → guest-auth-provider-UMP8Z_bK.js} +2 -2
  21. package/dist/{guest-auth-provider-peD5Zasl.js.map → guest-auth-provider-UMP8Z_bK.js.map} +1 -1
  22. package/dist/{index-DpJl_Cpa.mjs → index-Cgbr0abz.mjs} +2190 -2340
  23. package/dist/index-Cgbr0abz.mjs.map +1 -0
  24. package/dist/{index-DEDYfO2p.js → index-FkEGh6dx.js} +2192 -2344
  25. package/dist/index-FkEGh6dx.js.map +1 -0
  26. package/dist/index.d.ts +0 -1
  27. package/dist/index.js +1 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +1 -1
  30. package/dist/{index.native-BSheiGj2.js → index.native-CAHw3k3_.js} +621 -323
  31. package/dist/index.native-CAHw3k3_.js.map +1 -0
  32. package/dist/{index.native-BIUs8t7F.mjs → index.native-wyaknPBR.mjs} +619 -319
  33. package/dist/index.native-wyaknPBR.mjs.map +1 -0
  34. package/dist/index.native.d.ts +0 -1
  35. package/dist/index.native.js +1 -2
  36. package/dist/index.native.js.map +1 -1
  37. package/dist/index.native.mjs +1 -1
  38. package/dist/{injected-evm-wallet-provider-DnOrVMDX.mjs → injected-evm-wallet-provider-B8x7dNuL.mjs} +2 -2
  39. package/dist/{injected-evm-wallet-provider-DnOrVMDX.mjs.map → injected-evm-wallet-provider-B8x7dNuL.mjs.map} +1 -1
  40. package/dist/{injected-evm-wallet-provider-7XawIUdJ.js → injected-evm-wallet-provider-BAToKWZo.js} +2 -2
  41. package/dist/{injected-evm-wallet-provider-7XawIUdJ.js.map → injected-evm-wallet-provider-BAToKWZo.js.map} +1 -1
  42. package/dist/{injected-evm-wallet-provider-B-9VXQcH.js → injected-evm-wallet-provider-D-7Eg6TS.js} +2 -2
  43. package/dist/{injected-evm-wallet-provider-B-9VXQcH.js.map → injected-evm-wallet-provider-D-7Eg6TS.js.map} +1 -1
  44. package/dist/{injected-evm-wallet-provider-BVsxOzYr.mjs → injected-evm-wallet-provider-pVKJtDHL.mjs} +2 -2
  45. package/dist/{injected-evm-wallet-provider-BVsxOzYr.mjs.map → injected-evm-wallet-provider-pVKJtDHL.mjs.map} +1 -1
  46. package/dist/{injected-wallet-provider-D574oPzH.js → injected-wallet-provider-28GE3rH9.js} +8 -75
  47. package/dist/injected-wallet-provider-28GE3rH9.js.map +1 -0
  48. package/dist/{injected-wallet-provider-ZkPzVj5W.mjs → injected-wallet-provider-5NeXEzui.mjs} +9 -76
  49. package/dist/injected-wallet-provider-5NeXEzui.mjs.map +1 -0
  50. package/dist/{injected-wallet-provider-Bqk33sir.mjs → injected-wallet-provider-BojBmTAQ.mjs} +9 -76
  51. package/dist/injected-wallet-provider-BojBmTAQ.mjs.map +1 -0
  52. package/dist/{injected-wallet-provider-BMDJntMD.js → injected-wallet-provider-DvwlPJea.js} +8 -75
  53. package/dist/injected-wallet-provider-DvwlPJea.js.map +1 -0
  54. package/dist/onramp.d.ts +4 -1
  55. package/package.json +2 -2
  56. package/dist/auth/crossmint-signer-bridge.d.ts +0 -45
  57. package/dist/auth/providers/phantom-wallet-provider.d.ts +0 -111
  58. package/dist/auth/providers/privy-wallet-provider.d.ts +0 -83
  59. package/dist/auth/providers/sol/sol-utils.d.ts +0 -2
  60. package/dist/auth/providers/solana-mobile-wallet-provider.d.ts +0 -151
  61. package/dist/email-auth-provider-BttT2sZK.js.map +0 -1
  62. package/dist/email-auth-provider-BxV1zZA_.js.map +0 -1
  63. package/dist/email-auth-provider-Cb4VKA1E.mjs.map +0 -1
  64. package/dist/email-auth-provider-vWv0rnbd.mjs.map +0 -1
  65. package/dist/index-DEDYfO2p.js.map +0 -1
  66. package/dist/index-DpJl_Cpa.mjs.map +0 -1
  67. package/dist/index.native-BIUs8t7F.mjs.map +0 -1
  68. package/dist/index.native-BSheiGj2.js.map +0 -1
  69. package/dist/injected-wallet-provider-BMDJntMD.js.map +0 -1
  70. package/dist/injected-wallet-provider-Bqk33sir.mjs.map +0 -1
  71. package/dist/injected-wallet-provider-D574oPzH.js.map +0 -1
  72. package/dist/injected-wallet-provider-ZkPzVj5W.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { assertCanonicalBoundedRuntimeWriteCalldata, getActiveSessionManager, getConfig, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, onSessionCleared, reconnectWithNewAuth, deriveUserIdentityFromIdToken, subscribe, getIdToken as getIdToken$1, convertRemainingAccounts, buildSetDocumentsTransaction, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
1
+ import { getConfig, getActiveSessionManager, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, onSessionCleared, reconnectWithNewAuth, deriveUserIdentityFromIdToken, subscribe, getIdToken as getIdToken$1, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
2
2
  import * as React from 'react';
3
3
 
4
4
  /**
@@ -847,6 +847,8 @@ function getDefaultExportFromCjs (x) {
847
847
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
848
848
  }
849
849
 
850
+ var safeBuffer = {exports: {}};
851
+
850
852
  var buffer = {};
851
853
 
852
854
  var base64Js = {};
@@ -3216,10 +3218,6 @@ function requireBuffer () {
3216
3218
  return buffer;
3217
3219
  }
3218
3220
 
3219
- var bufferExports = requireBuffer();
3220
-
3221
- var safeBuffer = {exports: {}};
3222
-
3223
3221
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
3224
3222
 
3225
3223
  var hasRequiredSafeBuffer;
@@ -3433,173 +3431,505 @@ var basex = /*@__PURE__*/getDefaultExportFromCjs(srcExports);
3433
3431
  var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
3434
3432
  var bs58 = basex(ALPHABET);
3435
3433
 
3436
- // crossmint-signer-bridge.ts — routes embedded-wallet signing through the
3437
- // Bounded-owned signer popup (auth.bounded.sh/wallet/signer).
3434
+ var bufferExports = requireBuffer();
3435
+
3436
+ // bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
3437
+ // and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
3438
+ // widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
3439
+ // bottom-sheet on mobile, a height tween as content swaps, and a clean close.
3440
+ //
3441
+ // It hosts EITHER:
3442
+ // - NATIVE content (the login card the SDK renders itself), or
3443
+ // - an IFRAME (the Bounded-owned signer page, which must run on the single
3444
+ // signer origin because a WebAuthn passkey is bound to one origin).
3445
+ //
3446
+ // The modal is a warm singleton: it stays mounted (hidden) between uses so a
3447
+ // second open - or a signature right after login - is instant and, when it is
3448
+ // already open, a clean in-place view swap rather than a reopen.
3449
+ function hasDOM$3() {
3450
+ return typeof window !== "undefined" && typeof document !== "undefined";
3451
+ }
3452
+ const OVERLAY_CSS = `
3453
+ :host{all:initial}
3454
+ *{box-sizing:border-box}
3455
+ .bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
3456
+ font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
3457
+ .bw-ov.mounted{display:block}
3458
+ .bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
3459
+ transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
3460
+ .bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
3461
+ .bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
3462
+ border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
3463
+ box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
3464
+ opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
3465
+ transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
3466
+ height .3s cubic-bezier(.2,.8,.2,1)}
3467
+ .bw-ov.show .bw-back{opacity:1}
3468
+ .bw-ov.show .bw-panel{opacity:1;transform:none}
3469
+ .bw-ov.closing .bw-back{opacity:0}
3470
+ .bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
3471
+ .bw-content{position:absolute;inset:0;overflow:hidden}
3472
+ .bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
3473
+ /* editorial corner crop-marks, matching the reference frame */
3474
+ .bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
3475
+ border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
3476
+ .bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
3477
+ .bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
3478
+ .bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
3479
+ .bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
3480
+ @media(max-width:560px){
3481
+ .bw-wrap{align-items:flex-end;padding:0}
3482
+ .bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
3483
+ .bw-content{overflow-y:auto}
3484
+ .bw-ov.show .bw-panel{transform:none}
3485
+ .bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
3486
+ }
3487
+ @media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
3488
+ `;
3489
+ let modal = null;
3490
+ function getModal() {
3491
+ if (modal)
3492
+ return modal;
3493
+ const root = document.createElement("div");
3494
+ root.setAttribute("data-bounded-widget", "");
3495
+ const shadow = root.attachShadow({ mode: "open" });
3496
+ const style = document.createElement("style");
3497
+ style.textContent = OVERLAY_CSS;
3498
+ shadow.appendChild(style);
3499
+ const overlay = document.createElement("div");
3500
+ overlay.className = "bw-ov";
3501
+ const back = document.createElement("div");
3502
+ back.className = "bw-back";
3503
+ const wrap = document.createElement("div");
3504
+ wrap.className = "bw-wrap";
3505
+ const panel = document.createElement("div");
3506
+ panel.className = "bw-panel";
3507
+ const content = document.createElement("div");
3508
+ content.className = "bw-content";
3509
+ panel.appendChild(content);
3510
+ for (const corner of ["tl", "tr", "bl", "br"]) {
3511
+ const mk = document.createElement("span");
3512
+ mk.className = "bw-mk " + corner;
3513
+ panel.appendChild(mk);
3514
+ }
3515
+ wrap.appendChild(panel);
3516
+ overlay.appendChild(back);
3517
+ overlay.appendChild(wrap);
3518
+ shadow.appendChild(overlay);
3519
+ (document.body || document.documentElement).appendChild(root);
3520
+ const m = {
3521
+ root,
3522
+ overlay,
3523
+ panel,
3524
+ content,
3525
+ iframe: null,
3526
+ iframeOrigin: "",
3527
+ iframeReady: false,
3528
+ onBackdrop: null,
3529
+ onIframeMessage: null,
3530
+ chain: Promise.resolve(),
3531
+ fitObserver: null,
3532
+ };
3533
+ // The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
3534
+ // that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
3535
+ // outside the panel. Fire only when the click lands on the wrap itself, never
3536
+ // when it bubbles up from the panel or its content.
3537
+ wrap.addEventListener("click", (event) => {
3538
+ if (event.target === wrap && m.onBackdrop)
3539
+ m.onBackdrop();
3540
+ });
3541
+ window.addEventListener("message", (event) => {
3542
+ if (!m.iframe)
3543
+ return;
3544
+ if (event.origin !== m.iframeOrigin)
3545
+ return;
3546
+ if (event.source !== m.iframe.contentWindow)
3547
+ return;
3548
+ const d = event.data;
3549
+ if (!d || typeof d !== "object")
3550
+ return;
3551
+ if (m.onIframeMessage)
3552
+ m.onIframeMessage(d);
3553
+ });
3554
+ modal = m;
3555
+ return m;
3556
+ }
3557
+ function showModal(m) {
3558
+ m.overlay.classList.remove("closing");
3559
+ m.overlay.classList.add("mounted");
3560
+ // Force a reflow so the .show transition runs from the hidden state every open.
3561
+ void m.panel.offsetWidth;
3562
+ m.overlay.classList.add("show");
3563
+ }
3564
+ function hideModal(m, after) {
3565
+ m.overlay.classList.remove("show");
3566
+ m.overlay.classList.add("closing");
3567
+ window.setTimeout(() => {
3568
+ m.overlay.classList.remove("mounted", "closing");
3569
+ }, 260);
3570
+ }
3571
+ function setPanelHeight(m, px) {
3572
+ const h = Math.max(140, Math.min(680, Math.round(px)));
3573
+ m.panel.style.height = h + "px";
3574
+ }
3575
+ /** Get (or lazily create) the warm iframe for signer/hosted-page mode. */
3576
+ function ensureIframe(m, url, origin) {
3577
+ var _a;
3578
+ if (m.iframe && m.iframeOrigin === origin && m.iframe.src === url)
3579
+ return m.iframe;
3580
+ detachIframe(m);
3581
+ (_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
3582
+ m.fitObserver = null;
3583
+ m.content.style.display = "none";
3584
+ const iframe = document.createElement("iframe");
3585
+ iframe.className = "bw-frame";
3586
+ iframe.setAttribute("title", "Bounded");
3587
+ // Grant the framed origin WebAuthn get + create so passkey enroll/sign run
3588
+ // inside the frame from the user's click (transient activation stays in-frame).
3589
+ iframe.setAttribute("allow", "publickey-credentials-get; publickey-credentials-create; clipboard-write");
3590
+ iframe.src = url;
3591
+ m.iframeOrigin = origin;
3592
+ m.iframeReady = false;
3593
+ m.panel.appendChild(iframe);
3594
+ m.iframe = iframe;
3595
+ return iframe;
3596
+ }
3597
+ function detachIframe(m) {
3598
+ if (m.iframe) {
3599
+ try {
3600
+ m.iframe.remove();
3601
+ }
3602
+ catch (_a) {
3603
+ /* ignore */
3604
+ }
3605
+ }
3606
+ m.iframe = null;
3607
+ m.iframeReady = false;
3608
+ m.iframeOrigin = "";
3609
+ m.onIframeMessage = null;
3610
+ }
3611
+
3612
+ // turnkey-wallet-widget.ts - runs one Turnkey signer exchange inside the shared
3613
+ // in-app WIDGET (bounded-widget-modal.ts). The Bounded-owned signer page is loaded
3614
+ // in a warm iframe behind the modal so signing feels native: an in-app card plus
3615
+ // the OS Face ID / Touch ID sheet, never a jarring popup.
3438
3616
  //
3439
- // Slice-1 attached a non-custodial Crossmint smart wallet to email logins and
3440
- // populated `@user.address`. This bridge is the slice-2 counterpart: a user
3441
- // whose address came from `auth.wallets` can APPROVE transactions with that
3442
- // wallet from ANY app, non-custodially, by having the Crossmint client SDK run
3443
- // ONLY on the Bounded signer page (one whitelisted origin) and talking to it via
3444
- // postMessage.
3617
+ // WHY AN IFRAME: a WebAuthn passkey is bound to ONE origin, so passkey create/sign
3618
+ // MUST run on the single Bounded signer origin. The iframe carries the
3619
+ // publickey-credentials-{get,create} permission policy and the user's click lands
3620
+ // INSIDE it, giving WebAuthn the transient activation it needs.
3445
3621
  //
3446
- // SEMANTICS (Crossmint Solana smart wallets): these wallets cannot produce raw
3447
- // detached signatures — there is no signMessage and no sign-without-submit. The
3448
- // only honest primitive is EXECUTION (sign + submit) via the wallet's email-OTP
3449
- // authorization. So this bridge implements `signAndSubmitTransaction` and a
3450
- // token-send; `signMessage` / `signTransaction` throw a clear unsupported error.
3451
- const EMBEDDED_WALLET_UNSUPPORTED = 'Unsupported for embedded smart wallets — Crossmint Solana smart wallets sign ' +
3452
- 'and submit atomically. Use signAndSubmitTransaction() (or bounded.sendWalletToken) ' +
3453
- 'instead of signMessage/signTransaction.';
3454
- const NO_EMBEDDED_WALLET = 'This login has no embedded wallet. Enable `"auth": { "wallets": true }` in ' +
3455
- 'the app policy so email logins get a non-custodial wallet (@user.address), ' +
3456
- 'then sign from a user gesture.';
3622
+ // SAFARI / restricted embedders: cross-origin iframe passkey CREATE is unreliable
3623
+ // in Safari. The signer detects that and posts `fallbackPopup`; we then rerun the
3624
+ // SAME request through `popupFallback` (a real window). Callers pass a popup
3625
+ // implementation and never see the difference.
3626
+ /**
3627
+ * Run one signer exchange through the in-app widget. Resolves when `onResult`
3628
+ * accepts a result; falls back to `popupFallback` if the frame cannot run WebAuthn
3629
+ * (Safari cross-origin create) or cannot be mounted.
3630
+ */
3631
+ async function runWidget(url, signerOrigin, ex, popupFallback) {
3632
+ if (!hasDOM$3())
3633
+ return popupFallback();
3634
+ const m = getModal();
3635
+ const requestId = String(ex.request.requestId);
3636
+ // Serialize: WebAuthn is modal, and one warm frame handles one request at a time.
3637
+ const run = m.chain.then(() => new Promise((resolve, reject) => {
3638
+ let settled = false;
3639
+ let usedFallback = false;
3640
+ const iframe = ensureIframe(m, url, signerOrigin);
3641
+ const send = () => {
3642
+ if (!iframe.contentWindow)
3643
+ return;
3644
+ try {
3645
+ iframe.contentWindow.postMessage(ex.request, signerOrigin);
3646
+ }
3647
+ catch (_a) {
3648
+ /* frame gone */
3649
+ }
3650
+ };
3651
+ let loadTimer = 0;
3652
+ let ceilingTimer = 0;
3653
+ const finish = (fn) => {
3654
+ if (settled)
3655
+ return;
3656
+ settled = true;
3657
+ m.onIframeMessage = null;
3658
+ m.onBackdrop = null;
3659
+ window.clearTimeout(loadTimer);
3660
+ window.clearTimeout(ceilingTimer);
3661
+ fn();
3662
+ };
3663
+ const goFallback = () => {
3664
+ if (usedFallback || settled)
3665
+ return;
3666
+ usedFallback = true;
3667
+ hideModal(m);
3668
+ finish(() => popupFallback().then(resolve, reject));
3669
+ };
3670
+ m.onBackdrop = () => {
3671
+ hideModal(m);
3672
+ finish(() => reject(new Error("cancelled")));
3673
+ };
3674
+ m.onIframeMessage = (d) => {
3675
+ if (d.type === "bounded:turnkey:ready") {
3676
+ m.iframeReady = true;
3677
+ send();
3678
+ return;
3679
+ }
3680
+ if (d.type === "bounded:turnkey:resize" && typeof d.height === "number") {
3681
+ setPanelHeight(m, d.height);
3682
+ return;
3683
+ }
3684
+ if (d.type === "bounded:turnkey:fallbackPopup" && d.requestId === requestId) {
3685
+ goFallback();
3686
+ return;
3687
+ }
3688
+ if (d.type === "bounded:turnkey:result" && d.requestId === requestId) {
3689
+ const outcome = ex.onResult(d);
3690
+ if ("error" in outcome) {
3691
+ hideModal(m);
3692
+ finish(() => reject(new Error(outcome.error)));
3693
+ return;
3694
+ }
3695
+ if (outcome.done) {
3696
+ hideModal(m);
3697
+ finish(() => resolve(outcome.value));
3698
+ }
3699
+ }
3700
+ };
3701
+ showModal(m);
3702
+ if (m.iframeReady)
3703
+ send();
3704
+ // If the frame never loads, degrade to the popup (best-effort - a post-load
3705
+ // popup may be blocked without a gesture, in which case the fallback rejects).
3706
+ loadTimer = window.setTimeout(() => {
3707
+ if (!m.iframeReady && !settled)
3708
+ goFallback();
3709
+ }, 8000);
3710
+ // Absolute ceiling so a stuck flow cannot hang forever.
3711
+ ceilingTimer = window.setTimeout(() => {
3712
+ if (!settled) {
3713
+ hideModal(m);
3714
+ finish(() => reject(new Error("Wallet approval timed out.")));
3715
+ }
3716
+ }, 5 * 60 * 1000);
3717
+ }));
3718
+ m.chain = run.then(() => undefined, () => undefined);
3719
+ return run;
3720
+ }
3721
+
3722
+ // turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
3723
+ // kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
3724
+ //
3725
+ // The hosted Turnkey signer refuses to sign unless the request carries an issuer-signed
3726
+ // SIGNING CAPABILITY bound to the app id, the requesting origin, and the EXACT payload
3727
+ // hash. `requestTurnkeySigningCapability` mints one for the given bytes and
3728
+ // `buildTurnkeySignRequest` assembles the signer request that carries it. The bridge
3729
+ // (turnkey-signer-bridge.ts) supplies the base url + id token and routes through both,
3730
+ // so a regression here (a dropped capability, a wrong route, a swallowed error) is
3731
+ // caught without a browser.
3732
+ // POST the Bounded id token + payload hash to the issuer's capability mint route and
3733
+ // return the issuer-signed capability for exactly these bytes. Credential-free: identity
3734
+ // is proven with the id token in the body (not an ambient cookie the cross-origin fetch
3735
+ // would not carry). Throws with the same stable messages the bridge relied on inline.
3736
+ async function requestTurnkeySigningCapability(opts) {
3737
+ const base = opts.baseUrl.replace(/\/$/, '');
3738
+ let res;
3739
+ try {
3740
+ res = await opts.fetchImpl(`${base}/wallet/turnkey/capability`, {
3741
+ method: 'POST',
3742
+ headers: { 'content-type': 'application/json' },
3743
+ body: JSON.stringify({ idToken: opts.idToken, messageHex: opts.messageHex }),
3744
+ });
3745
+ }
3746
+ catch (_a) {
3747
+ throw new Error('Could not reach the Bounded signer to authorize this signature.');
3748
+ }
3749
+ if (!res.ok) {
3750
+ let error = 'signing_capability_denied';
3751
+ try {
3752
+ const d = (await res.json());
3753
+ if (d && typeof d.error === 'string')
3754
+ error = d.error;
3755
+ }
3756
+ catch (_b) {
3757
+ /* keep the default marker */
3758
+ }
3759
+ throw new Error(`Turnkey signing was not authorized (${error}).`);
3760
+ }
3761
+ const d = (await res.json());
3762
+ if (!d || typeof d.capability !== 'string' || !d.capability) {
3763
+ throw new Error('Turnkey signing capability was malformed.');
3764
+ }
3765
+ return d.capability;
3766
+ }
3767
+ // Assemble the `bounded:turnkey:sign` postMessage request, carrying the issuer-signed
3768
+ // capability for the same bytes. Without the capability the hosted signer refuses
3769
+ // (capability_required), so it is a required field of every sign request.
3770
+ function buildTurnkeySignRequest(opts) {
3771
+ return {
3772
+ type: 'bounded:turnkey:sign',
3773
+ requestId: opts.requestId,
3774
+ messageHex: opts.messageHex,
3775
+ capability: opts.capability,
3776
+ };
3777
+ }
3778
+
3779
+ // turnkey-signer-bridge.ts - routes NON-CUSTODIAL Turnkey embedded-wallet signing
3780
+ // through the Bounded-owned signer page (auth.bounded.sh/wallet/turnkey/signer).
3781
+ //
3782
+ // TRANSPORT: by default the signer runs inside the in-app WIDGET (turnkey-wallet-
3783
+ // widget.ts) - an iframe behind a Shadow-DOM modal that matches the Bounded widget
3784
+ // look + motion, so signing feels native. Safari (unreliable at iframe passkey
3785
+ // CREATE) and any environment where the frame cannot mount fall back to a real
3786
+ // popup window; both transports speak the same postMessage protocol.
3787
+ //
3788
+ // Turnkey wallets produce raw detached signatures. The signer runs the Turnkey
3789
+ // browser SDK on the single Bounded origin and stamps an ed25519 signRawPayload
3790
+ // with the user's passkey. The app retains the transaction itself.
3791
+ //
3792
+ // ONE ORIGIN, ONE PASSKEY: a WebAuthn passkey is bound to a single origin, so ALL
3793
+ // Bounded apps (any subdomain / custom domain) sign through this one origin - giving
3794
+ // every user one wallet + one passkey that roams across every app.
3457
3795
  function hasDOM$2() {
3458
3796
  return typeof window !== 'undefined' && typeof document !== 'undefined';
3459
3797
  }
3798
+ // Safari (incl. iOS WebViews) is unreliable at running a WebAuthn CREATE inside a
3799
+ // cross-origin iframe, so the widget's automatic in-iframe passkey enrollment can
3800
+ // fail - and a post-failure popup would no longer have a user gesture to open with.
3801
+ // There we pick the popup up front, from the original click, so enrollment is solid.
3802
+ // Everywhere else the inline widget is used (buttery in-app modal).
3803
+ function preferPopup() {
3804
+ if (!hasDOM$2() || typeof navigator === 'undefined')
3805
+ return true;
3806
+ const ua = navigator.userAgent || '';
3807
+ return /^((?!chrome|android|crios|fxios|edg|edga|opr).)*safari/i.test(ua);
3808
+ }
3809
+ // Dispatch one signer exchange through the in-app WIDGET, degrading to the POPUP
3810
+ // when we cannot embed (Safari enrollment, or the frame never loads).
3811
+ async function deliver(url, origin, ex) {
3812
+ const popup = () => runPopup(url, origin, ex);
3813
+ if (preferPopup())
3814
+ return popup();
3815
+ return runWidget(url, origin, { request: ex.request, onResult: ex.onResult }, popup);
3816
+ }
3817
+ async function turnkeySignerPage() {
3818
+ const cfg = await getConfig();
3819
+ const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
3820
+ return { url: `${base}/wallet/turnkey/signer`, origin: new URL(base).origin };
3821
+ }
3822
+ function toHex(bytes) {
3823
+ let h = '';
3824
+ for (const b of bytes)
3825
+ h += b.toString(16).padStart(2, '0');
3826
+ return h;
3827
+ }
3828
+ function fromHex(hex) {
3829
+ const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
3830
+ const out = new Uint8Array(clean.length / 2);
3831
+ for (let i = 0; i < out.length; i++)
3832
+ out[i] = parseInt(clean.substr(i * 2, 2), 16);
3833
+ return out;
3834
+ }
3460
3835
  function decodeJwt$1(token) {
3461
3836
  try {
3462
- let b64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
3463
- const pad = b64.length % 4;
3464
- if (pad === 2)
3465
- b64 += '==';
3466
- else if (pad === 3)
3467
- b64 += '=';
3468
- return JSON.parse(bufferExports.Buffer.from(b64, 'base64').toString('utf8'));
3837
+ let value = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
3838
+ const padding = value.length % 4;
3839
+ if (padding === 2)
3840
+ value += '==';
3841
+ else if (padding === 3)
3842
+ value += '=';
3843
+ return JSON.parse(getPlatform().atob(value));
3469
3844
  }
3470
3845
  catch (_a) {
3471
3846
  return {};
3472
3847
  }
3473
3848
  }
3474
- // Pull the embedded-wallet identity out of the active session's id token.
3475
- // Returns null when this session has no wallet (so callers can throw the
3476
- // informative NO_EMBEDDED_WALLET error).
3477
- function walletClaimsFromSession(chain = 'solana') {
3478
- let idToken = null;
3849
+ function activeTurnkeyAddress() {
3850
+ let token = null;
3479
3851
  try {
3480
- idToken = getActiveSessionManager().getIdToken();
3852
+ token = getActiveSessionManager().getIdToken();
3481
3853
  }
3482
3854
  catch (_a) {
3483
- idToken = null;
3855
+ token = null;
3484
3856
  }
3485
- if (!idToken)
3486
- return null;
3487
- const c = decodeJwt$1(idToken);
3488
- const address = c['custom:walletAddress'];
3489
- const evmAddress = c['custom:evmWalletAddress'];
3490
- const email = c['email'];
3491
- if (typeof email !== 'string' || !email)
3857
+ if (!token)
3492
3858
  return null;
3493
- if (chain === 'solana' && (typeof address !== 'string' || !address))
3494
- return null;
3495
- if (chain === 'evm' && (typeof evmAddress !== 'string' || !/^0x[0-9a-fA-F]{40}$/.test(evmAddress)))
3496
- return null;
3497
- const envClaim = c['custom:walletEnvironment'];
3498
- const environment = envClaim === 'staging' || envClaim === 'production' ? envClaim : undefined;
3499
- return Object.assign(Object.assign(Object.assign({ email }, (typeof address === 'string' && address ? { address } : {})), (typeof evmAddress === 'string' ? { evmAddress: evmAddress.toLowerCase() } : {})), { environment });
3500
- }
3501
- async function signerPageUrl() {
3502
- const cfg = await getConfig();
3503
- const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
3504
- return { url: `${base}/wallet/signer`, origin: new URL(base).origin };
3859
+ const claims = decodeJwt$1(token);
3860
+ const address = claims['custom:walletAddress'];
3861
+ const provider = claims['custom:walletProvider'];
3862
+ return provider === 'turnkey' && typeof address === 'string' && address
3863
+ ? address
3864
+ : null;
3505
3865
  }
3506
3866
  let requestSeq = 0;
3507
- /**
3508
- * Open the Bounded signer popup, send `action` to it, and resolve with the
3509
- * submitted transaction result. Must be called from a USER GESTURE (click/tap) or
3510
- * the browser will block the popup — in which case this rejects with a clear
3511
- * "Popup blocked" error telling the app to call from a user gesture.
3512
- *
3513
- * Non-custodial: the popup gates signing behind an email OTP delivered to the
3514
- * wallet's owner, and validates that the wallet address matches the account.
3515
- */
3516
- async function signViaSignerPopup(action) {
3517
- if (!hasDOM$2()) {
3518
- throw new Error('Embedded-wallet signing is only available in the browser (popup-based).');
3519
- }
3520
- const isEvm = action.kind === 'evmTransaction';
3521
- const claims = walletClaimsFromSession(isEvm ? 'evm' : 'solana');
3522
- if (!claims)
3523
- throw new Error(NO_EMBEDDED_WALLET);
3524
- const { url, origin: signerOrigin } = await signerPageUrl();
3525
- const requestId = `bsign_${Date.now()}_${++requestSeq}`;
3526
- // Centered popup, sized for the OTP form (matches the OIDC login popup shape).
3527
- const w = 420, h = 600;
3867
+ // Shared popup driver: open the signer page, do the ready/postMessage handshake, and
3868
+ // resolve when `onResult` accepts a result message. Origin + source are pinned.
3869
+ async function runPopup(url, signerOrigin, ex) {
3870
+ var _a, _b;
3871
+ const w = (_a = ex.width) !== null && _a !== void 0 ? _a : 420, h = (_b = ex.height) !== null && _b !== void 0 ? _b : 620;
3528
3872
  const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
3529
3873
  const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
3530
- const popup = window.open(url, 'bounded-wallet-signer', `width=${w},height=${h},left=${left},top=${top}`);
3874
+ const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
3531
3875
  if (!popup) {
3532
- throw new Error('Popup blocked — call signAndSubmitTransaction() from a user gesture (e.g. a click handler) so the wallet approval window can open.');
3533
- }
3534
- const request = {
3535
- type: 'bounded:sign',
3536
- requestId,
3537
- environment: claims.environment, // may be undefined → page auto-detects
3538
- email: claims.email,
3539
- address: isEvm ? claims.evmAddress : claims.address,
3540
- action,
3541
- };
3876
+ throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the passkey approval window can open.');
3877
+ }
3542
3878
  return await new Promise((resolve, reject) => {
3543
3879
  let settled = false;
3544
- let closeTimer;
3545
3880
  let sentRequest = false;
3546
- const cleanup = () => {
3547
- settled = true;
3548
- window.removeEventListener('message', onMsg);
3549
- if (closeTimer)
3550
- clearInterval(closeTimer);
3551
- };
3552
- const sendRequest = () => {
3553
- if (sentRequest)
3881
+ let closeTimer;
3882
+ const cleanup = () => { settled = true; window.removeEventListener('message', onMsg); if (closeTimer)
3883
+ clearInterval(closeTimer); };
3884
+ const sendRequest = () => { if (sentRequest)
3885
+ return; sentRequest = true; try {
3886
+ popup.postMessage(ex.request, signerOrigin);
3887
+ }
3888
+ catch ( /* popup gone */_a) { /* popup gone */ } };
3889
+ const onMsg = (event) => {
3890
+ if (event.origin !== signerOrigin)
3554
3891
  return;
3555
- sentRequest = true;
3556
- try {
3557
- popup.postMessage(request, signerOrigin);
3558
- }
3559
- catch ( /* popup gone */_a) { /* popup gone */ }
3560
- };
3561
- const onMsg = (ev) => {
3562
- if (ev.origin !== signerOrigin)
3563
- return; // only trust the signer page origin
3564
- if (ev.source !== popup)
3565
- return; // only trust this signer popup window
3566
- const d = ev.data;
3892
+ if (event.source !== popup)
3893
+ return;
3894
+ const d = event.data;
3567
3895
  if (!d || typeof d !== 'object')
3568
3896
  return;
3569
- if (d.type === 'bounded:signer:ready') {
3897
+ if (d.type === 'bounded:turnkey:ready') {
3570
3898
  sendRequest();
3571
3899
  return;
3572
3900
  }
3573
- if (d.type === 'bounded:signer:result' && d.requestId === requestId) {
3574
- cleanup();
3575
- try {
3576
- popup.close();
3901
+ if (d.type === 'bounded:turnkey:result' && d.requestId === ex.request.requestId) {
3902
+ const outcome = ex.onResult(d);
3903
+ if ('error' in outcome) {
3904
+ cleanup();
3905
+ try {
3906
+ popup.close();
3907
+ }
3908
+ catch ( /* ignore */_a) { /* ignore */ }
3909
+ reject(new Error(outcome.error));
3910
+ return;
3911
+ }
3912
+ if (outcome.done) {
3913
+ cleanup();
3914
+ try {
3915
+ popup.close();
3916
+ }
3917
+ catch ( /* ignore */_b) { /* ignore */ }
3918
+ resolve(outcome.value);
3577
3919
  }
3578
- catch ( /* ignore */_a) { /* ignore */ }
3579
- if (d.ok)
3580
- resolve({ hash: d.hash, explorerLink: d.explorerLink, transactionId: d.transactionId });
3581
- else
3582
- reject(new Error(String(d.error || 'Wallet signing failed')));
3583
3920
  }
3584
3921
  };
3585
3922
  window.addEventListener('message', onMsg);
3586
- // Fallback: if the ready ping was missed (fast load / focus race), push the
3587
- // request shortly after opening. The page ignores it until a bounded:sign
3588
- // arrives and only acts on the first one, so a duplicate is harmless.
3589
3923
  setTimeout(sendRequest, 1200);
3590
- // If the user closes the popup before completing, reject (unless a result
3591
- // just arrived — give any in-flight message a beat to settle first).
3592
3924
  closeTimer = setInterval(() => {
3593
3925
  if (popup.closed) {
3594
3926
  clearInterval(closeTimer);
3595
3927
  setTimeout(() => { if (!settled) {
3596
3928
  cleanup();
3597
- reject(new Error('Wallet approval window was closed before signing completed.'));
3929
+ reject(new Error('Wallet approval window was closed before it completed.'));
3598
3930
  } }, 400);
3599
3931
  }
3600
3932
  }, 500);
3601
- // Overall safety timeout (OTP + signing). Generous — the user may take a
3602
- // while to fetch the code from their email.
3603
3933
  setTimeout(() => { if (!settled) {
3604
3934
  cleanup();
3605
3935
  try {
@@ -3610,80 +3940,135 @@ async function signViaSignerPopup(action) {
3610
3940
  } }, 5 * 60 * 1000);
3611
3941
  });
3612
3942
  }
3613
- // Serialize a legacy or versioned Solana transaction to BASE58 for the signer
3614
- // page. Crossmint's smart-wallet createTransaction endpoint requires the
3615
- // `serializedTransaction` string to be base58 — it rejects base64 with
3616
- // "Invalid base58 string" (verified against Crossmint staging 2026-07-07). The
3617
- // `wallets-sdk` object path bs58-encodes internally, so base58 is the contract.
3618
- //
3619
- // We do NOT require existing signatures (the smart wallet signs on the page);
3620
- // Crossmint extracts the instructions, overrides the fee payer to its sponsor,
3621
- // and re-wraps them for the smart wallet.
3622
- function serializeTransaction(transaction) {
3623
- const isLegacy = 'recentBlockhash' in transaction &&
3624
- !('message' in transaction && 'staticAccountKeys' in transaction.message);
3625
- const bytes = isLegacy
3626
- ? transaction.serialize({ requireAllSignatures: false, verifySignatures: false })
3627
- : transaction.serialize();
3628
- return bs58.encode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes));
3943
+ // AS-02: the hosted signer refuses to sign unless the request carries an issuer-signed
3944
+ // SIGNING CAPABILITY bound to the app id, this origin, and the EXACT bytes (a bare postMessage
3945
+ // from any peer can no longer consent-phish a signature). Fetch one from the human-auth issuer
3946
+ // for exactly these bytes before posting the sign request. Credential-free: the caller proves
3947
+ // identity with the Bounded id token WE minted (a body secret, RS256-verified server-side), not
3948
+ // an ambient cookie - the issuer's session cookie is SameSite-scoped to its own origin and is
3949
+ // not sent on this cross-origin fetch, so the token is what carries the session's wallet.
3950
+ async function fetchSigningCapability(messageHex) {
3951
+ const cfg = await getConfig();
3952
+ const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
3953
+ let idToken = null;
3954
+ try {
3955
+ idToken = getActiveSessionManager().getIdToken();
3956
+ }
3957
+ catch (_a) {
3958
+ idToken = null;
3959
+ }
3960
+ if (!idToken) {
3961
+ throw new Error('This session has no Turnkey wallet. Sign in again to provision one.');
3962
+ }
3963
+ return requestTurnkeySigningCapability({
3964
+ baseUrl: base,
3965
+ idToken,
3966
+ messageHex,
3967
+ fetchImpl: (url, init) => fetch(url, init),
3968
+ });
3969
+ }
3970
+ function hasTurnkeyWallet() {
3971
+ return activeTurnkeyAddress() !== null;
3629
3972
  }
3630
3973
  /**
3631
- * Whether the active session carries an embedded wallet (so the provider should
3632
- * route signing through the popup rather than throw "no wallet").
3974
+ * Open the Bounded Turnkey signer popup and sign `message` (raw bytes) with the
3975
+ * user's non-custodial Turnkey wallet, resolving with the ed25519 signature +
3976
+ * the wallet address. MUST be called from a USER GESTURE (click/tap) or the
3977
+ * browser blocks the popup.
3978
+ *
3979
+ * On the user's FIRST signature the popup transparently enrolls a device passkey
3980
+ * (Face ID / Touch ID) and creates their wallet - no email code. Subsequent
3981
+ * signatures are just a passkey approval.
3633
3982
  */
3634
- function hasEmbeddedWallet() {
3635
- return walletClaimsFromSession() != null;
3636
- }
3637
- /** Whether the active email/social session carries an embedded Crossmint EVM wallet. */
3638
- function hasEmbeddedEvmWallet() {
3639
- return walletClaimsFromSession('evm') != null;
3640
- }
3641
- /** signAndSubmitTransaction for embedded wallets: serialize + route through the
3642
- * signer popup, resolve with the on-chain transaction signature/hash. */
3643
- async function embeddedSignAndSubmit(transaction) {
3644
- const serializedTransaction = serializeTransaction(transaction);
3645
- const result = await signViaSignerPopup({ kind: 'transaction', serializedTransaction });
3646
- const hash = result.hash || result.transactionId;
3647
- if (!hash)
3648
- throw new Error('Signer returned no transaction hash.');
3649
- return hash;
3650
- }
3651
- /** Submit a validated BoundedRuntime EVM write through the user's email signer. */
3652
- async function embeddedRunEvmTransaction(transaction) {
3653
- var _a, _b;
3654
- if (!transaction.to || !transaction.data || !transaction.chainId)
3655
- throw new Error('Incomplete embedded EVM transaction');
3656
- if (!/^0x[0-9a-fA-F]{40}$/.test(transaction.to))
3657
- throw new Error('Invalid embedded EVM runtime address');
3658
- assertCanonicalBoundedRuntimeWriteCalldata(transaction.data);
3659
- if (!Number.isSafeInteger(transaction.chainId) || transaction.chainId <= 0)
3660
- throw new Error('Invalid embedded EVM chain id');
3661
- if (transaction.nonce !== undefined)
3662
- throw new Error('Server-supplied EVM nonces are not accepted');
3663
- try {
3664
- if (BigInt((_a = transaction.value) !== null && _a !== void 0 ? _a : '0') !== BigInt(0))
3665
- throw new Error('Bounded EVM writes cannot transfer native value');
3983
+ async function signSolanaMessageViaTurnkey(message) {
3984
+ if (!hasDOM$2()) {
3985
+ throw new Error('Turnkey embedded-wallet signing is only available in the browser (popup-based).');
3986
+ }
3987
+ const { url, origin } = await turnkeySignerPage();
3988
+ const requestId = `tksign_${Date.now()}_${++requestSeq}`;
3989
+ const messageHex = toHex(message);
3990
+ // AS-02: obtain the issuer-signed capability for THESE exact bytes first; the signer will
3991
+ // refuse (capability_required) without it. Fetched per signature (each is single-payload).
3992
+ const capability = await fetchSigningCapability(messageHex);
3993
+ return await deliver(url, origin, {
3994
+ request: buildTurnkeySignRequest({ requestId, messageHex, capability }),
3995
+ onResult: (d) => {
3996
+ if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
3997
+ return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
3998
+ }
3999
+ return { error: String(d.error || 'Turnkey signing failed') };
4000
+ },
4001
+ });
4002
+ }
4003
+ function requireMatchingAddress(result) {
4004
+ const expected = activeTurnkeyAddress();
4005
+ if (!expected)
4006
+ throw new Error('This session has no Turnkey wallet. Sign in again to provision one.');
4007
+ if (result.address !== expected)
4008
+ throw new Error('Turnkey signer returned a different wallet address than the active session.');
4009
+ return expected;
4010
+ }
4011
+ async function signMessageWithTurnkey(message) {
4012
+ const result = await signSolanaMessageViaTurnkey(getPlatform().textEncode(message));
4013
+ requireMatchingAddress(result);
4014
+ return bs58.encode(result.signature);
4015
+ }
4016
+ async function signTransactionWithTurnkey(transaction) {
4017
+ const isLegacy = 'serializeMessage' in transaction;
4018
+ const message = isLegacy
4019
+ ? transaction.serializeMessage()
4020
+ : transaction.message.serialize();
4021
+ const result = await signSolanaMessageViaTurnkey(new Uint8Array(message));
4022
+ const address = requireMatchingAddress(result);
4023
+ const { PublicKey } = await import('@solana/web3.js');
4024
+ if (isLegacy) {
4025
+ transaction.addSignature(new PublicKey(address), bufferExports.Buffer.from(result.signature));
3666
4026
  }
3667
- catch (error) {
3668
- if (error instanceof Error && error.message.includes('cannot transfer'))
3669
- throw error;
3670
- throw new Error('Invalid embedded EVM transaction value');
4027
+ else {
4028
+ transaction.addSignature(new PublicKey(address), result.signature);
4029
+ }
4030
+ return transaction;
4031
+ }
4032
+ async function signAndSubmitTransactionWithTurnkey(transaction, feePayer) {
4033
+ var _a;
4034
+ const config = await getConfig();
4035
+ const rpcUrl = (_a = config.rpcUrl) === null || _a === void 0 ? void 0 : _a.trim();
4036
+ if (!rpcUrl)
4037
+ throw new Error('Turnkey transaction submission requires init({ rpcUrl }).');
4038
+ const { Connection, PublicKey } = await import('@solana/web3.js');
4039
+ const connection = new Connection(rpcUrl, 'confirmed');
4040
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
4041
+ const isLegacy = 'serializeMessage' in transaction;
4042
+ if (isLegacy) {
4043
+ const legacy = transaction;
4044
+ legacy.recentBlockhash = blockhash;
4045
+ legacy.lastValidBlockHeight = lastValidBlockHeight;
4046
+ if (!legacy.feePayer) {
4047
+ const address = activeTurnkeyAddress();
4048
+ if (!address)
4049
+ throw new Error('This session has no Turnkey wallet. Sign in again to provision one.');
4050
+ legacy.feePayer = feePayer !== null && feePayer !== void 0 ? feePayer : new PublicKey(address);
4051
+ }
4052
+ }
4053
+ else {
4054
+ transaction.message.recentBlockhash = blockhash;
3671
4055
  }
3672
- const result = await signViaSignerPopup({
3673
- kind: 'evmTransaction',
3674
- transaction: { to: transaction.to, data: transaction.data, value: (_b = transaction.value) !== null && _b !== void 0 ? _b : '0', chainId: transaction.chainId },
4056
+ const signed = await signTransactionWithTurnkey(transaction);
4057
+ const signature = await connection.sendRawTransaction(signed.serialize(), {
4058
+ skipPreflight: false,
4059
+ maxRetries: 3,
3675
4060
  });
3676
- const hash = result.hash || result.transactionId;
3677
- if (!hash)
3678
- throw new Error('Signer returned no EVM transaction hash.');
3679
- return { transactionSignature: hash, blockNumber: 0, gasUsed: '0', data: result };
4061
+ const confirmation = await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
4062
+ if (confirmation.value.err) {
4063
+ throw new Error(`Turnkey transaction failed: ${JSON.stringify(confirmation.value.err)}`);
4064
+ }
4065
+ return signature;
3680
4066
  }
3681
4067
 
3682
4068
  // Same message the email provider uses when a hosted (OIDC) login has no
3683
4069
  // embedded wallet — enable `auth.wallets` in policy to get one.
3684
- const OIDC_NO_WALLET = 'This login has no embedded wallet. Enable `"auth": { "wallets": true }` in ' +
3685
- 'the app policy so logins get a non-custodial Crossmint wallet (@user.address), ' +
3686
- 'then sign from a user gesture.';
4070
+ const OIDC_NO_WALLET = 'This session has no Turnkey wallet. Sign in again to provision the default ' +
4071
+ 'embedded wallet, or check whether the app explicitly sets auth.wallets:false.';
3687
4072
  const PKCE_STORE_KEY = 'bounded.oidc.pkce';
3688
4073
  const STORED_AUTH_METHOD_KEY$1 = 'bounded_last_auth_method';
3689
4074
  const ISSUER_SESSION_KIND_KEY$1 = 'bounded_issuer_session_kind';
@@ -3709,10 +4094,8 @@ function decodeJwt(token) {
3709
4094
  }
3710
4095
  }
3711
4096
  // A minimal AuthProvider so getSession()/logout() work for OIDC sessions, mirroring email.
3712
- // Signing surface (slice 2): a hosted login on a wallets-enabled app carries an
3713
- // embedded Crossmint wallet (@user.address). signAndSubmitTransaction routes
3714
- // through the Bounded signer popup; signMessage/signTransaction are structurally
3715
- // unsupported for Crossmint smart wallets (sign+submit atomically, no raw sig).
4097
+ // Turnkey supplies the default Solana wallet and supports raw message and transaction
4098
+ // signatures through the Bounded signer window.
3716
4099
  const oidcProviderSingleton = {
3717
4100
  async login() {
3718
4101
  const idToken = sessionManager().getIdToken();
@@ -3723,37 +4106,38 @@ const oidcProviderSingleton = {
3723
4106
  return idToken ? userFromIdToken(idToken) : null;
3724
4107
  },
3725
4108
  async logout() { await sessionManager().clearSession(); },
3726
- async signMessage(_message) {
3727
- throw new Error(hasEmbeddedWallet() ? EMBEDDED_WALLET_UNSUPPORTED : OIDC_NO_WALLET);
4109
+ async signMessage(message) {
4110
+ if (!hasTurnkeyWallet())
4111
+ throw new Error(OIDC_NO_WALLET);
4112
+ return signMessageWithTurnkey(message);
3728
4113
  },
3729
- async signTransaction(_t) {
3730
- throw new Error(hasEmbeddedWallet() ? EMBEDDED_WALLET_UNSUPPORTED : OIDC_NO_WALLET);
4114
+ async signTransaction(transaction) {
4115
+ if (!hasTurnkeyWallet())
4116
+ throw new Error(OIDC_NO_WALLET);
4117
+ return signTransactionWithTurnkey(transaction);
3731
4118
  },
3732
- async runTransaction(evm, sol, options) {
4119
+ async runTransaction(evm, sol, _options) {
3733
4120
  if (evm && sol)
3734
4121
  throw new Error('A transaction cannot target EVM and Solana at the same time.');
3735
- if (!evm)
4122
+ if (!hasTurnkeyWallet())
3736
4123
  throw new Error(OIDC_NO_WALLET);
3737
- if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false)
3738
- throw new Error('Embedded EVM wallets sign and submit atomically; shouldSubmitTx:false is unsupported.');
3739
- if (!hasEmbeddedEvmWallet())
3740
- throw new Error('This login has no embedded EVM wallet. Enable auth.wallets on an app with an EVM collection and sign in again.');
3741
- return embeddedRunEvmTransaction(evm);
4124
+ if (evm)
4125
+ throw new Error('Turnkey embedded wallets currently support Solana only. Connect an EVM wallet for EVM transactions.');
4126
+ throw new Error('This Solana transaction shape is not supported by the Turnkey provider. Use a pre-built transaction.');
3742
4127
  },
3743
- async signAndSubmitTransaction(transaction, _feePayer) {
3744
- if (!hasEmbeddedWallet())
4128
+ async signAndSubmitTransaction(transaction, feePayer) {
4129
+ if (!hasTurnkeyWallet())
3745
4130
  throw new Error(OIDC_NO_WALLET);
3746
- return embeddedSignAndSubmit(transaction);
4131
+ return signAndSubmitTransactionWithTurnkey(transaction, feePayer);
3747
4132
  },
3748
4133
  };
3749
4134
  function userFromIdToken(idToken) {
3750
4135
  const c = decodeJwt(idToken);
3751
4136
  const id = c['custom:userId'] || c.sub || c['custom:walletAddress'] || '';
3752
4137
  return {
3753
- id, address: (c['custom:walletAddress'] || null),
3754
- evmAddress: typeof c['custom:evmWalletAddress'] === 'string' && /^0x[0-9a-fA-F]{40}$/.test(c['custom:evmWalletAddress'])
3755
- ? c['custom:evmWalletAddress'].toLowerCase()
3756
- : null,
4138
+ id,
4139
+ address: (c['custom:walletProvider'] === 'turnkey' && c['custom:walletAddress'] || null),
4140
+ evmAddress: null,
3757
4141
  email: (c.email || null), provider: oidcProviderSingleton,
3758
4142
  };
3759
4143
  }
@@ -4476,7 +4860,7 @@ async function getAuthProvider(config) {
4476
4860
  // manager as legacy email sessions. Human login should be started
4477
4861
  // with loginWithRedirect(); this provider is kept as the restore and
4478
4862
  // logout surface for those sessions.
4479
- currentAuthProvider = new (await import('./email-auth-provider-Cb4VKA1E.mjs')).EmailAuthProvider();
4863
+ currentAuthProvider = new (await import('./email-auth-provider-wqNimboO.mjs')).EmailAuthProvider();
4480
4864
  break;
4481
4865
  case "wallet":
4482
4866
  case "phantom": {
@@ -4495,7 +4879,7 @@ async function getAuthProvider(config) {
4495
4879
  if (!isWalletLoginEnabled(config)) {
4496
4880
  throw walletLoginNotEnabledError(authMethod);
4497
4881
  }
4498
- const { InjectedWalletProvider } = await import('./injected-wallet-provider-ZkPzVj5W.mjs');
4882
+ const { InjectedWalletProvider } = await import('./injected-wallet-provider-5NeXEzui.mjs');
4499
4883
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
4500
4884
  break;
4501
4885
  }
@@ -4508,7 +4892,7 @@ async function getAuthProvider(config) {
4508
4892
  // Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
4509
4893
  // to point at a specific wallet or set chainId/domain.
4510
4894
  const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
4511
- const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-DnOrVMDX.mjs');
4895
+ const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-B8x7dNuL.mjs');
4512
4896
  currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
4513
4897
  break;
4514
4898
  }
@@ -4536,7 +4920,7 @@ async function getAuthProvider(config) {
4536
4920
  break;
4537
4921
  case "guest":
4538
4922
  // Anonymous auth: a device-local ed25519 keypair signs the challenge.
4539
- currentAuthProvider = new (await import('./guest-auth-provider-BBPpydtM.mjs')).GuestAuthProvider();
4923
+ currentAuthProvider = new (await import('./guest-auth-provider-C3EGlSSp.mjs')).GuestAuthProvider();
4540
4924
  break;
4541
4925
  }
4542
4926
  if (!currentAuthProvider) {
@@ -4592,7 +4976,7 @@ async function signInAnonymously() {
4592
4976
  // Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
4593
4977
  // the main bundle for data-only apps. Specifier is a static string literal
4594
4978
  // so bundle-splitting can see it.
4595
- const provider = new (await import('./guest-auth-provider-BBPpydtM.mjs')).GuestAuthProvider();
4979
+ const provider = new (await import('./guest-auth-provider-C3EGlSSp.mjs')).GuestAuthProvider();
4596
4980
  setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
4597
4981
  const commit = (user) => {
4598
4982
  // No await/interleaving inside this block: provider/method/config/marker
@@ -5830,18 +6214,6 @@ async function list() {
5830
6214
  */
5831
6215
  const apps = { create, list, buildCreateRedirectUrl };
5832
6216
 
5833
- async function deserializeTransaction(tx) {
5834
- // Literal specifier so bundlers can code-split the lazy chunk.
5835
- const { Transaction, VersionedTransaction } = await import('@solana/web3.js');
5836
- const buf = Buffer.isBuffer(tx) ? tx : Buffer.from(tx, 'base64');
5837
- if ((buf[0] & 0x80) !== 0) {
5838
- return VersionedTransaction.deserialize(buf);
5839
- }
5840
- else {
5841
- return Transaction.from(buf);
5842
- }
5843
- }
5844
-
5845
6217
  // bounded.onramp() - fund the signed-in user's wallet with Coinbase Onramp.
5846
6218
  //
5847
6219
  // Web-only. Opens Coinbase's hosted checkout (pay.coinbase.com) in a popup
@@ -5883,16 +6255,16 @@ async function readError(res) {
5883
6255
  }
5884
6256
  }
5885
6257
  function sessionBody(options, extra) {
5886
- 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));
6258
+ return JSON.stringify(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (options.asset ? { asset: options.asset } : {})), (options.amountUsd !== undefined ? { amountUsd: options.amountUsd } : {})), (options.minAmountOut !== undefined ? { minAmountOut: options.minAmountOut } : {})), (options.slippagePad !== undefined ? { slippagePad: options.slippagePad } : {})), extra));
5887
6259
  }
5888
- function resolveAddress(options) {
5889
- var _a, _b;
6260
+ function resolveAddress() {
6261
+ var _a;
5890
6262
  const user = getCurrentUser();
5891
- 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;
6263
+ const address = (_a = user === null || user === void 0 ? void 0 : user.address) !== null && _a !== void 0 ? _a : undefined;
5892
6264
  if (!address) {
5893
6265
  throw new Error(user
5894
- ? 'onramp: the signed-in account has no wallet address - pass { address } explicitly'
5895
- : 'onramp: no signed-in user - call login() first or pass { address }');
6266
+ ? 'onramp: the signed-in account has no wallet address (create one via the embedded wallet flow)'
6267
+ : 'onramp: no signed-in user - call login() first');
5896
6268
  }
5897
6269
  return address;
5898
6270
  }
@@ -5903,7 +6275,7 @@ function resolveAddress(options) {
5903
6275
  async function onrampQuote(options = {}) {
5904
6276
  if (!hasDOM())
5905
6277
  throw new Error('onrampQuote is web-only');
5906
- resolveAddress(options);
6278
+ resolveAddress();
5907
6279
  const res = await authedFetch('/__bounded/onramp/session', {
5908
6280
  method: 'POST',
5909
6281
  headers: { 'content-type': 'application/json' },
@@ -5961,7 +6333,7 @@ async function onramp(options = {}) {
5961
6333
  var _a;
5962
6334
  if (!hasDOM())
5963
6335
  throw new Error('onramp() is web-only');
5964
- resolveAddress(options);
6336
+ resolveAddress();
5965
6337
  // Open the window synchronously so the click's user-activation window is not
5966
6338
  // consumed by the fetch below (same hard-won pattern as loginWithPopup).
5967
6339
  let popup = null;
@@ -6184,84 +6556,12 @@ class PrivyExpoProvider {
6184
6556
  setCurrentUser(user);
6185
6557
  return user;
6186
6558
  }
6187
- async runTransaction(_evmTransactionData, solTransactionData, options) {
6188
- var _a;
6189
- await this.ensureReady();
6190
- const [{ Connection, PublicKey }, programSdk] = await Promise.all([
6191
- import('@solana/web3.js'),
6192
- import('@coral-xyz/anchor'),
6193
- ]);
6194
- if (!solTransactionData) {
6195
- throw new Error('Solana transaction data is required');
6196
- }
6197
- const wallet = await this.getWalletOrThrow();
6198
- const rpcUrl = this.getRpcUrl(solTransactionData.network);
6199
- const connection = new Connection(rpcUrl, 'confirmed');
6200
- const remainingAccounts = convertRemainingAccounts(solTransactionData.txArgs[0].remainingAccounts);
6201
- const app_id = solTransactionData.appId;
6202
- if (!app_id) {
6203
- throw new Error('App ID is required');
6204
- }
6205
- // Create a mock wallet adapter for Anchor (only publicKey needed for building TX)
6206
- const walletPublicKey = new PublicKey(wallet.address);
6207
- const walletAdapter = {
6208
- publicKey: walletPublicKey,
6209
- signTransaction: async (tx) => tx,
6210
- signAllTransactions: async (txs) => txs,
6211
- };
6212
- const ProviderClass = programSdk[['Anch', 'orProvider'].join('')];
6213
- const programProvider = new ProviderClass(connection, walletAdapter, ProviderClass.defaultOptions());
6214
- const finalDeduped = [];
6215
- for (const acc of remainingAccounts) {
6216
- const existing = finalDeduped.find((d) => d.pubkey.equals(acc.pubkey));
6217
- if (existing) {
6218
- existing.isSigner = existing.isSigner || acc.isSigner;
6219
- existing.isWritable = existing.isWritable || acc.isWritable;
6220
- }
6221
- else {
6222
- finalDeduped.push(acc);
6223
- }
6224
- }
6225
- const { tx, blockhash, lastValidBlockHeight } = await buildSetDocumentsTransaction(connection, solTransactionData.txArgs[0].idl, programProvider, walletPublicKey, {
6226
- app_id,
6227
- documents: solTransactionData.txArgs[0].setDocumentData,
6228
- delete_paths: solTransactionData.txArgs[0].deletePaths,
6229
- txData: solTransactionData.txArgs[0].txData,
6230
- }, finalDeduped, solTransactionData.lutKey, solTransactionData.preInstructions, false, solTransactionData.additionalLutAddresses);
6231
- // Set blockhash
6232
- const isLegacyTx = 'recentBlockhash' in tx &&
6233
- !('message' in tx && 'staticAccountKeys' in tx.message);
6234
- if (isLegacyTx) {
6235
- tx.recentBlockhash = blockhash;
6236
- tx.lastValidBlockHeight = lastValidBlockHeight;
6237
- }
6238
- else {
6239
- tx.message.recentBlockhash = blockhash;
6240
- }
6241
- try {
6242
- if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
6243
- // Sign only — don't submit
6244
- const signed = await wallet.signTransaction(tx);
6245
- return {
6246
- signedTransaction: signed.signedTransaction,
6247
- blockNumber: 0,
6248
- gasUsed: '0',
6249
- data: '',
6250
- };
6251
- }
6252
- // Sign and submit
6253
- const { signature, txInfo } = await this.signAndSubmitInternal(tx, wallet, connection);
6254
- return {
6255
- transactionSignature: signature,
6256
- blockNumber: (txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) || 0,
6257
- gasUsed: ((_a = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _a === void 0 ? void 0 : _a.fee.toString()) || '0',
6258
- data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
6259
- };
6260
- }
6261
- catch (err) {
6262
- console.error(err);
6263
- throw err;
6264
- }
6559
+ async runTransaction(_evmTransactionData, _solTransactionData, _options) {
6560
+ // Client-side Solana transaction building is retired: the realtime
6561
+ // worker returns a fully-built (and, when sponsored, partially-signed)
6562
+ // transaction on its 202, which the SDK validates and signs via
6563
+ // signTransaction. This provider serves no EVM payload either.
6564
+ throw new Error('runTransaction is not supported by this provider; Bounded returns pre-built Solana transactions that are signed with signTransaction.');
6265
6565
  }
6266
6566
  async signMessage(message) {
6267
6567
  await this.ensureReady();
@@ -6418,5 +6718,5 @@ class PrivyExpoProvider {
6418
6718
  }
6419
6719
  }
6420
6720
 
6421
- export { useAuth as A, useQuery as B, useBuildRun as C, apps as D, EMBEDDED_WALLET_UNSUPPORTED as E, deserializeTransaction as F, getIdToken as G, onramp as H, onrampQuote as I, onrampStatus as J, setPlatform as K, builds as L, BuildsApiError as M, NON_TERMINAL_STATES as N, OffchainAuthProvider as O, PrivyExpoProvider as P, PARK_REASONS as Q, RUN_STATES as R, FAILURE_REASONS as S, TERMINAL_STATES as T, BUILD_REVIEW_CAPABILITY_SCHEMA as U, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as V, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as W, hasEmbeddedEvmWallet as a, embeddedSignAndSubmit as b, setAuthLoading as c, bufferExports as d, embeddedRunEvmTransaction as e, bs58 as f, getPlatform as g, hasEmbeddedWallet as h, confirmAndCheckTransaction as i, init as j, getCurrentUser as k, onAuthLoadingChanged as l, getAuthLoading as m, normalizeSolanaRpcUrl as n, onAuthStateChanged as o, login as p, logout as q, resolveSolanaRpcUrl as r, setCurrentUser as s, getAuthProvider as t, signInAnonymously as u, loginWithPrivy as v, loginWithRedirect as w, completeLoginFromRedirect as x, loginWithPopup as y, completeLoginInPopup as z };
6422
- //# sourceMappingURL=index.native-BIUs8t7F.mjs.map
6721
+ export { useAuth as A, useQuery as B, useBuildRun as C, apps as D, getIdToken as E, onramp as F, onrampQuote as G, onrampStatus as H, setPlatform as I, builds as J, BuildsApiError as K, PARK_REASONS as L, FAILURE_REASONS as M, NON_TERMINAL_STATES as N, OffchainAuthProvider as O, PrivyExpoProvider as P, BUILD_REVIEW_CAPABILITY_SCHEMA as Q, RUN_STATES as R, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as S, TERMINAL_STATES as T, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as U, signMessageWithTurnkey as a, signTransactionWithTurnkey as b, signAndSubmitTransactionWithTurnkey as c, setAuthLoading as d, bufferExports as e, bs58 as f, getPlatform as g, hasTurnkeyWallet as h, confirmAndCheckTransaction as i, init as j, getCurrentUser as k, onAuthLoadingChanged as l, getAuthLoading as m, normalizeSolanaRpcUrl as n, onAuthStateChanged as o, login as p, logout as q, resolveSolanaRpcUrl as r, setCurrentUser as s, getAuthProvider as t, signInAnonymously as u, loginWithPrivy as v, loginWithRedirect as w, completeLoginFromRedirect as x, loginWithPopup as y, completeLoginInPopup as z };
6722
+ //# sourceMappingURL=index.native-wyaknPBR.mjs.map