@bounded-sh/client 0.0.114 → 0.0.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/providers/injected-wallet-types.d.ts +11 -2
- package/dist/auth/providers/wallet-standard-discovery.d.ts +2 -0
- package/dist/auth/turnkey-signer-window.d.ts +19 -0
- package/dist/auth/turnkey-signing-capability.d.ts +5 -1
- package/dist/{email-auth-provider-nDZjRDpj.mjs → email-auth-provider-ByNBWdfi.mjs} +2 -2
- package/dist/{email-auth-provider-Bk5GSpYT.mjs.map → email-auth-provider-ByNBWdfi.mjs.map} +1 -1
- package/dist/{email-auth-provider-Bk5GSpYT.mjs → email-auth-provider-CODHCC3h.mjs} +2 -2
- package/dist/{email-auth-provider-nDZjRDpj.mjs.map → email-auth-provider-CODHCC3h.mjs.map} +1 -1
- package/dist/{email-auth-provider-DeORQbTK.js → email-auth-provider-CPHrcy_b.js} +2 -2
- package/dist/{email-auth-provider-DeORQbTK.js.map → email-auth-provider-CPHrcy_b.js.map} +1 -1
- package/dist/{email-auth-provider-Cr66GaKw.js → email-auth-provider-CwG04f4u.js} +2 -2
- package/dist/{email-auth-provider-Cr66GaKw.js.map → email-auth-provider-CwG04f4u.js.map} +1 -1
- package/dist/{guest-auth-provider-CUNkZVFK.js → guest-auth-provider-3rfSwq3f.js} +2 -2
- package/dist/{guest-auth-provider-CUNkZVFK.js.map → guest-auth-provider-3rfSwq3f.js.map} +1 -1
- package/dist/{guest-auth-provider-ejUTbjCC.mjs → guest-auth-provider-CGN4k_LG.mjs} +2 -2
- package/dist/{guest-auth-provider-DDudqqxY.mjs.map → guest-auth-provider-CGN4k_LG.mjs.map} +1 -1
- package/dist/{guest-auth-provider-DDudqqxY.mjs → guest-auth-provider-PfOp_Yzj.mjs} +2 -2
- package/dist/{guest-auth-provider-ejUTbjCC.mjs.map → guest-auth-provider-PfOp_Yzj.mjs.map} +1 -1
- package/dist/{guest-auth-provider-5bX-xt6K.js → guest-auth-provider-d6dk97x6.js} +2 -2
- package/dist/{guest-auth-provider-5bX-xt6K.js.map → guest-auth-provider-d6dk97x6.js.map} +1 -1
- package/dist/{index-BAiLpNEQ.mjs → index-C63ceG_z.mjs} +789 -73
- package/dist/index-C63ceG_z.mjs.map +1 -0
- package/dist/{index-BZ0X1Z9X.js → index-nU0ZSfWs.js} +793 -71
- package/dist/index-nU0ZSfWs.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{index.native-DJSOqyKQ.js → index.native-BBLBnqal.js} +75 -22
- package/dist/index.native-BBLBnqal.js.map +1 -0
- package/dist/{index.native-DcW2klyn.mjs → index.native-DLAEWwuZ.mjs} +76 -23
- package/dist/index.native-DLAEWwuZ.mjs.map +1 -0
- package/dist/index.native.js +1 -1
- package/dist/index.native.mjs +1 -1
- package/dist/{injected-evm-wallet-provider-C4aKa2v1.mjs → injected-evm-wallet-provider-C4vNOS4P.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-C4aKa2v1.mjs.map → injected-evm-wallet-provider-C4vNOS4P.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-BDYWjIOQ.js → injected-evm-wallet-provider-C6KC7pJ6.js} +2 -2
- package/dist/{injected-evm-wallet-provider-BDYWjIOQ.js.map → injected-evm-wallet-provider-C6KC7pJ6.js.map} +1 -1
- package/dist/{injected-evm-wallet-provider-D06arDQD.js → injected-evm-wallet-provider-D1JA3cL4.js} +2 -2
- package/dist/{injected-evm-wallet-provider-D06arDQD.js.map → injected-evm-wallet-provider-D1JA3cL4.js.map} +1 -1
- package/dist/{injected-evm-wallet-provider-BQ_kG1vI.mjs → injected-evm-wallet-provider-D7rOyYdr.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-BQ_kG1vI.mjs.map → injected-evm-wallet-provider-D7rOyYdr.mjs.map} +1 -1
- package/dist/{injected-wallet-provider-Dj-F69gb.mjs → injected-wallet-provider-BLRrB2rb.mjs} +11 -5
- package/dist/{injected-wallet-provider-Dj-F69gb.mjs.map → injected-wallet-provider-BLRrB2rb.mjs.map} +1 -1
- package/dist/{injected-wallet-provider-B9jvzDM5.js → injected-wallet-provider-BXFicWTx.js} +11 -5
- package/dist/{injected-wallet-provider-B9jvzDM5.js.map → injected-wallet-provider-BXFicWTx.js.map} +1 -1
- package/dist/{injected-wallet-provider-Dqmk-m4V.js → injected-wallet-provider-BvlKZoVJ.js} +59 -34
- package/dist/injected-wallet-provider-BvlKZoVJ.js.map +1 -0
- package/dist/{injected-wallet-provider-DxPcYxP-.mjs → injected-wallet-provider-DruiX7GK.mjs} +59 -34
- package/dist/injected-wallet-provider-DruiX7GK.mjs.map +1 -0
- package/dist/venue-escrow.d.ts +113 -0
- package/package.json +2 -2
- package/dist/index-BAiLpNEQ.mjs.map +0 -1
- package/dist/index-BZ0X1Z9X.js.map +0 -1
- package/dist/index.native-DJSOqyKQ.js.map +0 -1
- package/dist/index.native-DcW2klyn.mjs.map +0 -1
- package/dist/injected-wallet-provider-Dqmk-m4V.js.map +0 -1
- package/dist/injected-wallet-provider-DxPcYxP-.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getConfig, getActiveSessionManager, WebSessionManager, ReactNativeSessionManager, revokeSession, reconnectWithNewAuth, deriveUserIdentityFromIdToken, init as init$2, onSessionCleared, subscribe, getFreshAuthToken, getSessionGeneration, getIdToken as getIdToken$1, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
|
|
1
|
+
import { getConfig, getActiveSessionManager, WebSessionManager, ReactNativeSessionManager, revokeSession, reconnectWithNewAuth, deriveUserIdentityFromIdToken, init as init$2, onSessionCleared, subscribe, getFreshAuthToken, getSessionGeneration, getIdToken as getIdToken$1, getConfiguredAppId, setWithVenueGrant, writeNotAppliedPhase, waitForSetResult, reconcileSetResult, resolveVenueRpcUrl, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
4
|
function walletContextFromClaims(claims, baseUrl, expectedAppId, tenantOrigin) {
|
|
@@ -594,6 +594,37 @@ class OffchainAuthProvider {
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
const SELECTED_WALLET_UNAVAILABLE_ERR = 'The selected Solana wallet is unavailable. Select it again before continuing.';
|
|
598
|
+
/** Check login capabilities without connecting, signing, or loading a wallet SDK.
|
|
599
|
+
* This checks login only; it does not promise message or transaction signing. */
|
|
600
|
+
function supportsWalletSignIn(provider) {
|
|
601
|
+
if (typeof provider !== 'object' || provider === null)
|
|
602
|
+
return false;
|
|
603
|
+
const wallet = provider;
|
|
604
|
+
return typeof wallet.connect === 'function' && typeof wallet.signIn === 'function';
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Default provider discovery: Phantom-first, then any Wallet-Standard
|
|
608
|
+
* `window.solana`. Kept tiny + synchronous (no heavy imports) so it works inside
|
|
609
|
+
* a user gesture and can be eagerly exported from the SDK entry. This names the
|
|
610
|
+
* default wallet even when it cannot sign in: login must refuse that wallet,
|
|
611
|
+
* never silently replace it with a different identity. Custom pickers can use
|
|
612
|
+
* supportsWalletSignIn() to decide which providers to offer for login.
|
|
613
|
+
*/
|
|
614
|
+
function defaultInjectedProvider() {
|
|
615
|
+
var _a;
|
|
616
|
+
if (typeof window === 'undefined')
|
|
617
|
+
return null;
|
|
618
|
+
const w = window;
|
|
619
|
+
const phantom = (_a = w === null || w === void 0 ? void 0 : w.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
620
|
+
if (phantom && typeof phantom.signMessage === 'function')
|
|
621
|
+
return phantom;
|
|
622
|
+
const generic = w === null || w === void 0 ? void 0 : w.solana;
|
|
623
|
+
if (generic && typeof generic.signMessage === 'function')
|
|
624
|
+
return generic;
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
|
|
597
628
|
// issuer-logout-bounce.ts - commit the top-level navigation that ends the hosted
|
|
598
629
|
// issuer's browser session (the Better Auth cookie on auth.bounded.sh).
|
|
599
630
|
//
|
|
@@ -4533,6 +4564,108 @@ async function openHiddenSignerFrame(page) {
|
|
|
4533
4564
|
};
|
|
4534
4565
|
}
|
|
4535
4566
|
|
|
4567
|
+
// turnkey-signer-window.ts - the top-level Bounded signer window: opening it, and reserving it
|
|
4568
|
+
// inside a click for an approval that is reached only after awaits.
|
|
4569
|
+
//
|
|
4570
|
+
// A cross-app Turnkey signature (the capability says `transport: 'popup'`) cannot run in the
|
|
4571
|
+
// in-app frame, so it opens in its own window, and a browser opens a window only inside the
|
|
4572
|
+
// user's click. The click handler therefore calls `reserveSignerPopup()` first, before any
|
|
4573
|
+
// `await`, and the exchange in turnkey-signer-bridge.ts navigates that window to the signer later.
|
|
4574
|
+
//
|
|
4575
|
+
// Kept out of the bridge on purpose: the bridge is loaded lazily as a whole module, so a bundler
|
|
4576
|
+
// keeps every one of its exports in every app that signs, while this reservation is needed only
|
|
4577
|
+
// by an app that funds an escrow.
|
|
4578
|
+
function openSignerWindow(url, w = 420, h = 620) {
|
|
4579
|
+
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
4580
|
+
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4581
|
+
return window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4582
|
+
}
|
|
4583
|
+
// One slot: the next popup exchange takes it, and a new reservation replaces an unused earlier one.
|
|
4584
|
+
// A property of a const rather than a reassigned `let`, which a bundler would keep every assignment
|
|
4585
|
+
// to, dragging the reservation into apps that never reserve.
|
|
4586
|
+
const reserved = { popup: null };
|
|
4587
|
+
/** Write `text` into the window; false when the window is not ours to write (it still works: it is navigated to the signer next). */
|
|
4588
|
+
function paint(popup, text) {
|
|
4589
|
+
try {
|
|
4590
|
+
const { document: doc } = popup;
|
|
4591
|
+
doc.title = 'Bounded';
|
|
4592
|
+
doc.documentElement.style.colorScheme = 'light dark';
|
|
4593
|
+
doc.body.style.cssText = 'margin:0;padding:24px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center;font:15px system-ui,sans-serif;background:Canvas;color:CanvasText';
|
|
4594
|
+
doc.body.textContent = text;
|
|
4595
|
+
return true;
|
|
4596
|
+
}
|
|
4597
|
+
catch (_a) {
|
|
4598
|
+
return false;
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
/**
|
|
4602
|
+
* Open the signer window NOW, synchronously, showing a neutral "Preparing your approval..."
|
|
4603
|
+
* page (a blank document with local text, no remote content). Call it from a user gesture,
|
|
4604
|
+
* before any `await`, and `release()` it when the flow ends. A cross-app Turnkey signature
|
|
4605
|
+
* then runs in this window instead of needing a fresh gesture.
|
|
4606
|
+
* Returns null when there is no DOM or the browser blocked the window.
|
|
4607
|
+
*/
|
|
4608
|
+
function reserveSignerPopup() {
|
|
4609
|
+
var _a;
|
|
4610
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
4611
|
+
return null;
|
|
4612
|
+
try {
|
|
4613
|
+
(_a = reserved.popup) === null || _a === void 0 ? void 0 : _a.close();
|
|
4614
|
+
}
|
|
4615
|
+
catch ( /* already closed */_b) { /* already closed */ }
|
|
4616
|
+
let popup = null;
|
|
4617
|
+
try {
|
|
4618
|
+
popup = openSignerWindow('');
|
|
4619
|
+
}
|
|
4620
|
+
catch ( /* a sandboxed page may throw instead of returning null */_c) { /* a sandboxed page may throw instead of returning null */ }
|
|
4621
|
+
reserved.popup = popup;
|
|
4622
|
+
if (!popup)
|
|
4623
|
+
return null;
|
|
4624
|
+
paint(popup, 'Preparing your approval...');
|
|
4625
|
+
return {
|
|
4626
|
+
release(message) {
|
|
4627
|
+
const unused = reserved.popup === popup;
|
|
4628
|
+
if (unused)
|
|
4629
|
+
reserved.popup = null;
|
|
4630
|
+
try {
|
|
4631
|
+
if (!message || !unused || popup.closed)
|
|
4632
|
+
return popup.close();
|
|
4633
|
+
const text = message.slice(0, 160);
|
|
4634
|
+
if (!paint(popup, text))
|
|
4635
|
+
return popup.close(); // not a window of ours to write on, so no message to linger on
|
|
4636
|
+
// Close it only while it still shows this text: a window that was reserved again or sent on to
|
|
4637
|
+
// the signer in the meantime is no longer ours to close.
|
|
4638
|
+
setTimeout(() => { try {
|
|
4639
|
+
if (popup.document.body.textContent === text)
|
|
4640
|
+
popup.close();
|
|
4641
|
+
}
|
|
4642
|
+
catch ( /* sent on to the signer */_a) { /* sent on to the signer */ } }, 1500);
|
|
4643
|
+
}
|
|
4644
|
+
catch ( /* already closed */_a) { /* already closed */ }
|
|
4645
|
+
},
|
|
4646
|
+
};
|
|
4647
|
+
}
|
|
4648
|
+
/** Hand the reserved window, if one is held, to an exchange: navigated to the signer page at `url`. */
|
|
4649
|
+
function takeReservedPopup(url) {
|
|
4650
|
+
const popup = reserved.popup;
|
|
4651
|
+
reserved.popup = null;
|
|
4652
|
+
if (!popup)
|
|
4653
|
+
return null;
|
|
4654
|
+
if (popup.closed)
|
|
4655
|
+
throw new Error('Wallet approval window was closed before it completed.');
|
|
4656
|
+
try {
|
|
4657
|
+
popup.location.replace(url);
|
|
4658
|
+
}
|
|
4659
|
+
catch (_a) {
|
|
4660
|
+
try {
|
|
4661
|
+
popup.close();
|
|
4662
|
+
}
|
|
4663
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
4664
|
+
throw new Error('Wallet approval window could not open the signer - retry from a click.');
|
|
4665
|
+
}
|
|
4666
|
+
return popup;
|
|
4667
|
+
}
|
|
4668
|
+
|
|
4536
4669
|
// turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
|
|
4537
4670
|
// kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
|
|
4538
4671
|
//
|
|
@@ -4544,9 +4677,10 @@ async function openHiddenSignerFrame(page) {
|
|
|
4544
4677
|
// so a regression here (a dropped capability, a wrong route, a swallowed error) is
|
|
4545
4678
|
// caught without a browser.
|
|
4546
4679
|
// POST the Bounded id token + payload hash to the issuer's capability mint route and
|
|
4547
|
-
// return the issuer-signed capability for exactly these bytes
|
|
4548
|
-
// is proven with the id token in the
|
|
4549
|
-
// would not carry). Throws with the same
|
|
4680
|
+
// return the issuer-signed capability for exactly these bytes, with the transport the
|
|
4681
|
+
// issuer requires for them. Credential-free: identity is proven with the id token in the
|
|
4682
|
+
// body (not an ambient cookie the cross-origin fetch would not carry). Throws with the same
|
|
4683
|
+
// stable messages the bridge relied on inline.
|
|
4550
4684
|
async function requestTurnkeySigningCapability(opts) {
|
|
4551
4685
|
const base = opts.baseUrl.replace(/\/$/, '');
|
|
4552
4686
|
let res;
|
|
@@ -4576,7 +4710,7 @@ async function requestTurnkeySigningCapability(opts) {
|
|
|
4576
4710
|
if (!d || typeof d.capability !== 'string' || !d.capability) {
|
|
4577
4711
|
throw new Error('Turnkey signing capability was malformed.');
|
|
4578
4712
|
}
|
|
4579
|
-
return d.capability;
|
|
4713
|
+
return d.transport === 'popup' ? { capability: d.capability, transport: 'popup' } : { capability: d.capability };
|
|
4580
4714
|
}
|
|
4581
4715
|
// Assemble the `bounded:turnkey:sign` postMessage request, carrying the issuer-signed
|
|
4582
4716
|
// capability for the same bytes. Without the capability the hosted signer refuses
|
|
@@ -4667,12 +4801,16 @@ function hasDOM$3() {
|
|
|
4667
4801
|
// when we cannot embed (no DOM, or the frame never mounts/loads). Nothing in the
|
|
4668
4802
|
// email-session flow needs a popup by preference: the code entry and the approval
|
|
4669
4803
|
// click both run fine inside a cross-origin iframe on every engine (the old
|
|
4670
|
-
// Safari popup preference existed only for in-iframe WebAuthn CREATE).
|
|
4804
|
+
// Safari popup preference existed only for in-iframe WebAuthn CREATE). The one
|
|
4805
|
+
// exception is a signature the issuer marks `transport: 'popup'` (a cross-app
|
|
4806
|
+
// write): the hosted signer refuses it inside any frame, so it never touches the widget.
|
|
4671
4807
|
async function deliver(url, origin, ex) {
|
|
4672
4808
|
var _a, _b;
|
|
4673
4809
|
const popup = () => runPopup(url, origin, ex);
|
|
4674
4810
|
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4675
|
-
const result =
|
|
4811
|
+
const result = ex.transport === 'popup'
|
|
4812
|
+
? await popup()
|
|
4813
|
+
: await runWidget(url, origin, { request: ex.request, onResult: ex.onResult, context: ex.context, assertCurrent: ex.assertCurrent }, popup);
|
|
4676
4814
|
(_b = ex.assertCurrent) === null || _b === void 0 ? void 0 : _b.call(ex);
|
|
4677
4815
|
return result;
|
|
4678
4816
|
}
|
|
@@ -4744,14 +4882,12 @@ function activeTurnkeyAddress() {
|
|
|
4744
4882
|
: null;
|
|
4745
4883
|
}
|
|
4746
4884
|
let requestSeq = 0;
|
|
4747
|
-
// Shared popup driver: open the signer page
|
|
4748
|
-
// resolve when `onResult` accepts a result message. Origin +
|
|
4885
|
+
// Shared popup driver: open the signer page (in the reserved window when one is held), do the
|
|
4886
|
+
// ready/postMessage handshake, and resolve when `onResult` accepts a result message. Origin +
|
|
4887
|
+
// source are pinned.
|
|
4749
4888
|
async function runPopup(url, signerOrigin, ex) {
|
|
4750
|
-
var _a
|
|
4751
|
-
const
|
|
4752
|
-
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
4753
|
-
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4754
|
-
const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4889
|
+
var _a;
|
|
4890
|
+
const popup = (_a = takeReservedPopup(url)) !== null && _a !== void 0 ? _a : openSignerWindow(url, ex.width, ex.height);
|
|
4755
4891
|
if (!popup) {
|
|
4756
4892
|
throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the wallet approval window can open.');
|
|
4757
4893
|
}
|
|
@@ -5001,7 +5137,7 @@ async function signSolanaMessageViaTurnkey(message) {
|
|
|
5001
5137
|
const messageHex = toHex(message);
|
|
5002
5138
|
// AS-02: obtain the issuer-signed capability for THESE exact bytes first; the signer will
|
|
5003
5139
|
// refuse (capability_required) without it. Fetched per signature (each is single-payload).
|
|
5004
|
-
const capability = await fetchSigningCapability(messageHex, context);
|
|
5140
|
+
const { capability, transport } = await fetchSigningCapability(messageHex, context);
|
|
5005
5141
|
// Inline (authMode:"turnkey") sessions plant no Better Auth cookie on the issuer, so the
|
|
5006
5142
|
// signer's same-origin status/create fetches need a bearer session proof: attach the
|
|
5007
5143
|
// createWallet action authorization (it covers the sign flow's get-or-create too). Minted
|
|
@@ -5010,11 +5146,20 @@ async function signSolanaMessageViaTurnkey(message) {
|
|
|
5010
5146
|
return await deliver(url, origin, {
|
|
5011
5147
|
context: context.key, assertCurrent: context.assertCurrent,
|
|
5012
5148
|
request: buildTurnkeySignRequest({ requestId, messageHex, capability, authorization }),
|
|
5149
|
+
transport,
|
|
5013
5150
|
onResult: (d) => {
|
|
5014
5151
|
if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
|
|
5015
|
-
|
|
5152
|
+
// A top-level popup keeps its signing session in the signer origin's own storage, which the
|
|
5153
|
+
// warm in-app frame (a partitioned context) cannot see, so it says nothing about that frame.
|
|
5154
|
+
if (transport !== 'popup')
|
|
5155
|
+
setWalletSessionState(context.key, { status: 'ready', expiresAt: Number(d.signingSessionExpiresAt) });
|
|
5016
5156
|
return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
5017
5157
|
}
|
|
5158
|
+
// The hosted signer refuses a cross-app signature inside a frame. Never retry it from here:
|
|
5159
|
+
// the top-level window needs the user's own click.
|
|
5160
|
+
if (d.error === 'top_level_required') {
|
|
5161
|
+
return { error: 'top_level_required: an approval that writes to another app must open in its own window. Start it from a click handler.' };
|
|
5162
|
+
}
|
|
5018
5163
|
return { error: String(d.error || 'Turnkey signing failed') };
|
|
5019
5164
|
},
|
|
5020
5165
|
});
|
|
@@ -6330,7 +6475,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6330
6475
|
// manager as legacy email sessions. Human login should be started
|
|
6331
6476
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
6332
6477
|
// logout surface for those sessions.
|
|
6333
|
-
currentAuthProvider = new (await import('./email-auth-provider-
|
|
6478
|
+
currentAuthProvider = new (await import('./email-auth-provider-ByNBWdfi.mjs')).EmailAuthProvider();
|
|
6334
6479
|
break;
|
|
6335
6480
|
case "phantom": {
|
|
6336
6481
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -6372,7 +6517,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6372
6517
|
// (bounded-login-widget.ts), so registration never counts as
|
|
6373
6518
|
// evidence the user has a wallet.
|
|
6374
6519
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
6375
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
6520
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-BLRrB2rb.mjs');
|
|
6376
6521
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
6377
6522
|
break;
|
|
6378
6523
|
}
|
|
@@ -6385,7 +6530,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6385
6530
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
6386
6531
|
// to point at a specific wallet or set chainId/domain.
|
|
6387
6532
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
6388
|
-
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-
|
|
6533
|
+
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-C4vNOS4P.mjs');
|
|
6389
6534
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
6390
6535
|
break;
|
|
6391
6536
|
}
|
|
@@ -6410,7 +6555,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6410
6555
|
break;
|
|
6411
6556
|
case "guest":
|
|
6412
6557
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
6413
|
-
currentAuthProvider = new (await import('./guest-auth-provider-
|
|
6558
|
+
currentAuthProvider = new (await import('./guest-auth-provider-CGN4k_LG.mjs')).GuestAuthProvider();
|
|
6414
6559
|
break;
|
|
6415
6560
|
}
|
|
6416
6561
|
if (!currentAuthProvider) {
|
|
@@ -6472,7 +6617,7 @@ async function signInAnonymously() {
|
|
|
6472
6617
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
6473
6618
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
6474
6619
|
// so bundle-splitting can see it.
|
|
6475
|
-
const provider = new (await import('./guest-auth-provider-
|
|
6620
|
+
const provider = new (await import('./guest-auth-provider-CGN4k_LG.mjs')).GuestAuthProvider();
|
|
6476
6621
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
6477
6622
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
6478
6623
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6643,7 +6788,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6643
6788
|
if (!sharedWalletReady) {
|
|
6644
6789
|
sharedWalletReady = (async () => {
|
|
6645
6790
|
await getConfig();
|
|
6646
|
-
await import('./injected-wallet-provider-
|
|
6791
|
+
await import('./injected-wallet-provider-BLRrB2rb.mjs');
|
|
6647
6792
|
})().catch((error) => {
|
|
6648
6793
|
sharedWalletReady = null;
|
|
6649
6794
|
throw error;
|
|
@@ -6725,10 +6870,12 @@ async function loginWithWallet(options) {
|
|
|
6725
6870
|
// one that yields nothing and then a wallet prepares the mobile registry
|
|
6726
6871
|
// after the tap for a wallet it never uses.
|
|
6727
6872
|
const pinnedProvider = (_b = (_a = injectedConfig.getProvider) === null || _a === void 0 ? void 0 : _a.call(injectedConfig)) !== null && _b !== void 0 ? _b : null;
|
|
6873
|
+
if (injectedConfig.getProvider && !pinnedProvider)
|
|
6874
|
+
throw new Error(SELECTED_WALLET_UNAVAILABLE_ERR);
|
|
6728
6875
|
const attemptConfig = Object.assign(Object.assign({}, injectedConfig), {
|
|
6729
|
-
//
|
|
6730
|
-
//
|
|
6731
|
-
getProvider: () => pinnedProvider });
|
|
6876
|
+
// An explicit choice stays pinned. With no getter, retain default
|
|
6877
|
+
// discovery rather than manufacturing an unavailable explicit choice.
|
|
6878
|
+
getProvider: injectedConfig.getProvider ? () => pinnedProvider : undefined });
|
|
6732
6879
|
// Idempotent: a caller that prepared the lane (the widget, or an app that
|
|
6733
6880
|
// awaited ensureWalletLoginReady before enabling its button) has already
|
|
6734
6881
|
// paid for this, so nothing activation-sensitive happens after the tap. A
|
|
@@ -6737,16 +6884,16 @@ async function loginWithWallet(options) {
|
|
|
6737
6884
|
//
|
|
6738
6885
|
// "Pinned" is about the EFFECTIVE resolution, not whether a getter was
|
|
6739
6886
|
// passed: a configured walletLogin.getProvider pins just as much as a
|
|
6740
|
-
// per-call one
|
|
6741
|
-
//
|
|
6742
|
-
//
|
|
6887
|
+
// per-call one. An unavailable explicit choice was refused above. With a
|
|
6888
|
+
// real wallet in hand only the shared half is needed - re-attempting a
|
|
6889
|
+
// failed mobile registration would be
|
|
6743
6890
|
// exactly the work-after-the-tap this exists to avoid, for a wallet this
|
|
6744
6891
|
// login is not even using.
|
|
6745
6892
|
if (pinnedProvider)
|
|
6746
6893
|
await ensureSharedWalletLoginReady();
|
|
6747
6894
|
else
|
|
6748
6895
|
await ensureWalletLoginReady();
|
|
6749
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
6896
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-BLRrB2rb.mjs');
|
|
6750
6897
|
// Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
|
|
6751
6898
|
// set of methods, so cleanup below must speak to the provider that actually
|
|
6752
6899
|
// holds the config rather than to whatever is wrapping it.
|
|
@@ -7574,15 +7721,28 @@ function hasFeature(wallet, name) {
|
|
|
7574
7721
|
function supportsSolana(wallet) {
|
|
7575
7722
|
return wallet.chains.some((c) => c.startsWith("solana:"));
|
|
7576
7723
|
}
|
|
7577
|
-
/** A wallet
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
* refused with the reason spelled out rather than silently missing from the list.
|
|
7581
|
-
* Transaction features are optional and checked lazily at call time. */
|
|
7582
|
-
function isLoginCapable(wallet) {
|
|
7724
|
+
/** A Solana wallet the SDK can connect and use for message signing. */
|
|
7725
|
+
function isSolanaWallet(wallet) {
|
|
7726
|
+
var _a, _b;
|
|
7583
7727
|
return (supportsSolana(wallet) &&
|
|
7584
|
-
|
|
7585
|
-
|
|
7728
|
+
typeof ((_a = feature(wallet, StandardConnect)) === null || _a === void 0 ? void 0 : _a.connect) === "function" &&
|
|
7729
|
+
typeof ((_b = feature(wallet, SolanaSignMessage)) === null || _b === void 0 ? void 0 : _b.signMessage) === "function");
|
|
7730
|
+
}
|
|
7731
|
+
/** Login choices must support the same SIWS operation login() requires.
|
|
7732
|
+
* Transaction features remain optional and are checked at call time. */
|
|
7733
|
+
function isLoginCapable(wallet) {
|
|
7734
|
+
var _a, _b;
|
|
7735
|
+
return isSolanaWallet(wallet) && supportsWalletSignIn({
|
|
7736
|
+
connect: (_a = feature(wallet, StandardConnect)) === null || _a === void 0 ? void 0 : _a.connect,
|
|
7737
|
+
signIn: (_b = feature(wallet, SolanaSignIn)) === null || _b === void 0 ? void 0 : _b.signIn,
|
|
7738
|
+
});
|
|
7739
|
+
}
|
|
7740
|
+
/** Distinguish unsupported installed wallets from an empty registry in the UI. */
|
|
7741
|
+
function hasUnsupportedStandardWallets(configuredNetwork) {
|
|
7742
|
+
if (typeof window === "undefined")
|
|
7743
|
+
return false;
|
|
7744
|
+
const chain = configuredNetwork ? solanaChainOrNull(configuredNetwork) : null;
|
|
7745
|
+
return getWallets().get().some((wallet) => (isSolanaWallet(wallet) && (!chain || wallet.chains.includes(chain)) && !isLoginCapable(wallet)));
|
|
7586
7746
|
}
|
|
7587
7747
|
/** The Wallet-Standard chain id for a configured network, or null when it is
|
|
7588
7748
|
* absent/unsupported. Ordering must never throw - failing closed on an
|
|
@@ -7611,6 +7771,7 @@ function feature(wallet, name) {
|
|
|
7611
7771
|
* an unconfigured network fails closed at sign time rather than defaulting.
|
|
7612
7772
|
*/
|
|
7613
7773
|
function standardWalletToInjectedProvider(wallet, configuredNetwork) {
|
|
7774
|
+
var _a;
|
|
7614
7775
|
// The adapter holds NO account of its own: `wallet.accounts` is the Wallet
|
|
7615
7776
|
// Standard's live authorized-account view, kept in sync by the wallet itself
|
|
7616
7777
|
// (it emits standard:events 'change' when it moves). Reading it on every
|
|
@@ -7710,12 +7871,12 @@ function standardWalletToInjectedProvider(wallet, configuredNetwork) {
|
|
|
7710
7871
|
const disconnect = feature(wallet, StandardDisconnect);
|
|
7711
7872
|
if (disconnect)
|
|
7712
7873
|
await disconnect.disconnect();
|
|
7713
|
-
} }, (
|
|
7874
|
+
} }, (typeof ((_a = feature(wallet, SolanaSignIn)) === null || _a === void 0 ? void 0 : _a.signIn) === "function"
|
|
7714
7875
|
? {
|
|
7715
7876
|
async signIn(input) {
|
|
7716
7877
|
var _a;
|
|
7717
7878
|
const sign = feature(wallet, SolanaSignIn);
|
|
7718
|
-
if (
|
|
7879
|
+
if (typeof (sign === null || sign === void 0 ? void 0 : sign.signIn) !== "function")
|
|
7719
7880
|
throw new Error("Wallet does not support solana:signIn");
|
|
7720
7881
|
const open = currentAccount();
|
|
7721
7882
|
if (open && !accountSupports(open, SolanaSignIn))
|
|
@@ -8263,14 +8424,12 @@ function walletGlyph() {
|
|
|
8263
8424
|
return svg;
|
|
8264
8425
|
}
|
|
8265
8426
|
function isInjectedProvider(v) {
|
|
8266
|
-
return (
|
|
8267
|
-
v !== null &&
|
|
8268
|
-
typeof v.signMessage === "function");
|
|
8427
|
+
return supportsWalletSignIn(v);
|
|
8269
8428
|
}
|
|
8270
8429
|
// Enumerate native wallets via the Solana Wallet Standard registry (NOT a
|
|
8271
|
-
// hardcoded name list) -
|
|
8272
|
-
// real name + icon - and ALSO an older wallet that never
|
|
8273
|
-
// exposes `window.solana`.
|
|
8430
|
+
// hardcoded name list) - wallets that support Bounded sign-in show up with
|
|
8431
|
+
// their real name + icon - and ALSO an eligible older wallet that never
|
|
8432
|
+
// registers and only exposes `window.solana`.
|
|
8274
8433
|
//
|
|
8275
8434
|
// The legacy `window.solana` probe fills in for a wallet too old to register
|
|
8276
8435
|
// itself. The rule it has always encoded is "no BROWSER wallet registered", and
|
|
@@ -8305,7 +8464,11 @@ const BUSY_MSG = "A sign-in is already in progress - finish or cancel it first."
|
|
|
8305
8464
|
// no wallet in this browser). A phone browser never sees installed wallet apps
|
|
8306
8465
|
// as injected providers, so the honest advice there is a different browser,
|
|
8307
8466
|
// not "install a wallet".
|
|
8308
|
-
function noLaneCopy(mobile) {
|
|
8467
|
+
function noLaneCopy(mobile, unsupportedWallets) {
|
|
8468
|
+
if (unsupportedWallets) {
|
|
8469
|
+
return "The wallets detected in this browser cannot sign in to Bounded. " +
|
|
8470
|
+
"Use a wallet that supports Sign In With Solana, then reload this page.";
|
|
8471
|
+
}
|
|
8309
8472
|
return mobile
|
|
8310
8473
|
? "No wallet is available in this browser. Open this page in your wallet app's " +
|
|
8311
8474
|
"browser, or on a desktop browser with a wallet extension, then try again."
|
|
@@ -8503,6 +8666,9 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8503
8666
|
// button with no wallet behind it.
|
|
8504
8667
|
if (wallets.length === 0)
|
|
8505
8668
|
walletEnabled = false;
|
|
8669
|
+
const defaultWallet = defaultInjectedProvider();
|
|
8670
|
+
const unsupportedWallets = !lane.hardOff && lane.requested !== false && (hasUnsupportedStandardWallets(network) ||
|
|
8671
|
+
(defaultWallet !== null && !supportsWalletSignIn(defaultWallet)));
|
|
8506
8672
|
const authMode = (_b = opts.authMode) !== null && _b !== void 0 ? _b : authApi.resolveAuthMode(initCfg);
|
|
8507
8673
|
// Widget text: per-call options win, then init() loginWidget config, then the
|
|
8508
8674
|
// defaults ("Sign in" / the Bounded subline; "" suppresses the subline).
|
|
@@ -8881,14 +9047,19 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8881
9047
|
// screen, and name the usual cause once in the console, where the
|
|
8882
9048
|
// developer's own preview run will show it.
|
|
8883
9049
|
if (!hasEmail && social.length === 0 && !walletEnabled) {
|
|
8884
|
-
card.appendChild(el("p", "sub", noLaneCopy(detectMobile())));
|
|
9050
|
+
card.appendChild(el("p", "sub", noLaneCopy(detectMobile(), unsupportedWallets)));
|
|
8885
9051
|
console.warn("[Bounded] openBoundedWidget has no sign-in lane to show: the app asked for " +
|
|
8886
|
-
"no email or social method and
|
|
8887
|
-
(
|
|
9052
|
+
"no email or social method and " +
|
|
9053
|
+
(unsupportedWallets
|
|
9054
|
+
? "the detected wallets do not support Sign In With Solana. "
|
|
9055
|
+
: "no wallet is available in this browser. ") +
|
|
9056
|
+
(offerMobile && !unsupportedWallets
|
|
8888
9057
|
? "On Android the Solana Mobile wallet is offered only when the app installs " +
|
|
8889
9058
|
"'@solana-mobile/wallet-standard-mobile' (see the warning above if registration failed); "
|
|
8890
9059
|
: "") +
|
|
8891
|
-
|
|
9060
|
+
(unsupportedWallets
|
|
9061
|
+
? "Add another sign-in method, or use a wallet that supports Sign In With Solana."
|
|
9062
|
+
: "Add a method, or make sure wallet login is enabled and the mobile adapter is installed."));
|
|
8892
9063
|
}
|
|
8893
9064
|
card.appendChild(status);
|
|
8894
9065
|
const foot = el("div", "foot");
|
|
@@ -10475,7 +10646,7 @@ async function onrampStatus(sinceMs) {
|
|
|
10475
10646
|
function isSuccess(t) {
|
|
10476
10647
|
return /SUCCESS|COMPLETED/i.test(t.status);
|
|
10477
10648
|
}
|
|
10478
|
-
function isPending(t) {
|
|
10649
|
+
function isPending$1(t) {
|
|
10479
10650
|
return /PROGRESS|PENDING|CREATED/i.test(t.status);
|
|
10480
10651
|
}
|
|
10481
10652
|
async function verifyCompletion(openedAt, asset, timeoutMs) {
|
|
@@ -10489,7 +10660,7 @@ async function verifyCompletion(openedAt, asset, timeoutMs) {
|
|
|
10489
10660
|
const done = txs.find((t) => isSuccess(t) && (t.asset === null || t.asset === asset));
|
|
10490
10661
|
if (done)
|
|
10491
10662
|
return done;
|
|
10492
|
-
sawPending = txs.some(isPending);
|
|
10663
|
+
sawPending = txs.some(isPending$1);
|
|
10493
10664
|
}
|
|
10494
10665
|
catch ( /* transient - keep polling until the deadline */_a) { /* transient - keep polling until the deadline */ }
|
|
10495
10666
|
// No completed fill and nothing in flight: one grace poll cycle still
|
|
@@ -10595,6 +10766,570 @@ async function onramp(options = {}) {
|
|
|
10595
10766
|
return Object.assign(Object.assign({}, base), { status: 'closed' });
|
|
10596
10767
|
}
|
|
10597
10768
|
|
|
10769
|
+
// venue-escrow.ts - fund and dispute a venue escrow from inside an OpenApps app (VENUE-GRANT-DESIGN.md).
|
|
10770
|
+
//
|
|
10771
|
+
// An OpenApps app's escrow lives in its venue (`agentescrows/<rootAppId>/e/<escrowId>`), and this
|
|
10772
|
+
// app's session cannot write another app's data. Bounded Auth therefore mints a 5 minute,
|
|
10773
|
+
// single-use GRANT for exactly one create (`POST {humanAuthApiUrl}/app-token/venue-grant`;
|
|
10774
|
+
// contract: packages/cdk/cloudflare/shared/venue-grant.ts, which the SDK cannot import because the
|
|
10775
|
+
// packages publish separately, so only what is needed is mirrored here), and the venue builds the
|
|
10776
|
+
// transaction for the wallet signed in here. The money's only authority is that wallet's
|
|
10777
|
+
// signature, checked onchain by the venue's own rule; the grant adds no power to the app.
|
|
10778
|
+
//
|
|
10779
|
+
// Funding moves money, so it keeps the recovery semantics oapps-fun's retired escrow page proved:
|
|
10780
|
+
// a pending record is saved BEFORE any network call and checked before ever funding again; the
|
|
10781
|
+
// escrow id is stable across retries; an uncertain broadcast is settled by its signature or the
|
|
10782
|
+
// public receipt and never re-sent; one tab funds at a time; and a row that does not match every
|
|
10783
|
+
// term is `taken`, never funded.
|
|
10784
|
+
/** Thrown by every venue escrow call. Branch on `code`; `error.name === 'VenueEscrowError'` also holds across bundled SDK copies. */
|
|
10785
|
+
class VenueEscrowError extends Error {
|
|
10786
|
+
constructor(code, message, details = {}) {
|
|
10787
|
+
super(message);
|
|
10788
|
+
this.name = 'VenueEscrowError';
|
|
10789
|
+
this.code = code;
|
|
10790
|
+
if (details.escrowId !== undefined)
|
|
10791
|
+
this.escrowId = details.escrowId;
|
|
10792
|
+
if (details.cause !== undefined)
|
|
10793
|
+
this.cause = details.cause;
|
|
10794
|
+
}
|
|
10795
|
+
}
|
|
10796
|
+
const ESCROW_SCHEMA = 'openapps.agent-escrow.v1';
|
|
10797
|
+
const STORAGE_PREFIX = 'bounded.venue-escrow-funding.v1';
|
|
10798
|
+
/** A saved funding that shows no row and no signature this long after its save can no longer land. */
|
|
10799
|
+
const ABANDONED_MS = 5 * 60000;
|
|
10800
|
+
const RECEIPT_POLL_MS = [0, 250, 500, 1000, 1500, 2500, 4000];
|
|
10801
|
+
const ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
10802
|
+
const APP_ID = /^[0-9a-f]{24}$/;
|
|
10803
|
+
const RECORD_ID = /^[0-9a-f]{32}$/;
|
|
10804
|
+
/**
|
|
10805
|
+
* Bounded Auth's own refusals of a grant mint, passed through as codes, with what each means to a
|
|
10806
|
+
* person: the error's message, and so what the reserved signer window shows before it closes.
|
|
10807
|
+
*/
|
|
10808
|
+
const GRANT_REFUSALS = {
|
|
10809
|
+
invalid_request: 'The request was not accepted. Update the app and try again.',
|
|
10810
|
+
invalid_token: 'Your session expired. Sign in again.',
|
|
10811
|
+
origin_not_allowed: "This site isn't allowed to fund escrows for this app.",
|
|
10812
|
+
not_an_openapps_app: "This app can't fund escrows.",
|
|
10813
|
+
wallet_required: 'Sign in with a wallet to fund escrows.',
|
|
10814
|
+
agent_wallet_refused: "An agent's wallet can't fund escrows here.",
|
|
10815
|
+
rate_limited: 'Too many attempts. Try again in a minute.',
|
|
10816
|
+
venue_grant_unavailable: 'Escrows are unavailable right now. Try again shortly.',
|
|
10817
|
+
};
|
|
10818
|
+
const nowSeconds = () => Math.floor(Date.now() / 1000);
|
|
10819
|
+
/** A v0 dispute transaction stops fitting past about 513 reason bytes; this keeps a margin. */
|
|
10820
|
+
const MAX_DISPUTE_REASON_BYTES = 400;
|
|
10821
|
+
const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
10822
|
+
const newRecordId = () => Array.from(getPlatform().getRandomBytes(16), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
10823
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10824
|
+
/** Mirrors `venueGrantPath` in shared/venue-grant.ts: the one path a grant of this kind may create. */
|
|
10825
|
+
function recordPath(kind, rootAppId, id) {
|
|
10826
|
+
return kind === 'escrow' ? `agentescrows/${rootAppId}/e/${id}` : `agentescrowdisputes/${rootAppId}/d/${id}`;
|
|
10827
|
+
}
|
|
10828
|
+
/** Who is signed in, read synchronously: the click handler must not await before it reserves the signer window. */
|
|
10829
|
+
function sessionIdentity() {
|
|
10830
|
+
const appId = getConfiguredAppId();
|
|
10831
|
+
let idToken = null;
|
|
10832
|
+
try {
|
|
10833
|
+
idToken = getActiveSessionManager().getIdToken();
|
|
10834
|
+
}
|
|
10835
|
+
catch ( /* no session store */_a) { /* no session store */ }
|
|
10836
|
+
if (!appId || !idToken)
|
|
10837
|
+
throw new VenueEscrowError('sign_in_required', 'Sign in before using venue escrows.');
|
|
10838
|
+
const wallet = deriveUserIdentityFromIdToken(idToken).address;
|
|
10839
|
+
if (!wallet)
|
|
10840
|
+
throw new VenueEscrowError('wallet_required', 'This session has no wallet to sign with.');
|
|
10841
|
+
return { appId, wallet };
|
|
10842
|
+
}
|
|
10843
|
+
async function mintGrant(kind, id, wallet) {
|
|
10844
|
+
var _a;
|
|
10845
|
+
let idToken = null;
|
|
10846
|
+
try {
|
|
10847
|
+
idToken = await getFreshAuthToken(false);
|
|
10848
|
+
}
|
|
10849
|
+
catch ( /* an expired session reads as signed out */_b) { /* an expired session reads as signed out */ }
|
|
10850
|
+
if (!idToken)
|
|
10851
|
+
throw new VenueEscrowError('sign_in_required', 'Your session expired. Sign in again.');
|
|
10852
|
+
if (deriveUserIdentityFromIdToken(idToken).address !== wallet) {
|
|
10853
|
+
throw new VenueEscrowError('wallet_changed', 'The signed-in wallet changed. Start again.');
|
|
10854
|
+
}
|
|
10855
|
+
const base = ((await getConfig()).humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
10856
|
+
let response;
|
|
10857
|
+
try {
|
|
10858
|
+
response = await fetch(`${base}/app-token/venue-grant`, {
|
|
10859
|
+
method: 'POST',
|
|
10860
|
+
headers: { 'content-type': 'application/json' },
|
|
10861
|
+
body: JSON.stringify({ idToken, kind, id, walletProtocol: 2 }),
|
|
10862
|
+
});
|
|
10863
|
+
}
|
|
10864
|
+
catch (cause) {
|
|
10865
|
+
throw new VenueEscrowError('venue_grant_unavailable', 'Could not reach Bounded Auth to authorize this write.', { cause });
|
|
10866
|
+
}
|
|
10867
|
+
const body = (await response.json().catch(() => null));
|
|
10868
|
+
if (!response.ok) {
|
|
10869
|
+
const error = typeof (body === null || body === void 0 ? void 0 : body.error) === 'string' ? body.error : '';
|
|
10870
|
+
const code = Object.keys(GRANT_REFUSALS).includes(error) ? error : response.status >= 500 ? 'venue_grant_unavailable' : 'venue_grant_refused';
|
|
10871
|
+
throw new VenueEscrowError(code, (_a = GRANT_REFUSALS[code]) !== null && _a !== void 0 ? _a : `Bounded Auth refused the venue grant (${error || response.status}).`);
|
|
10872
|
+
}
|
|
10873
|
+
const fields = body !== null && body !== void 0 ? body : {};
|
|
10874
|
+
const { grant, venueAppId, rootAppId, walletAddress, path } = fields;
|
|
10875
|
+
if (typeof grant !== 'string' || !grant || typeof venueAppId !== 'string' || !APP_ID.test(venueAppId)
|
|
10876
|
+
|| typeof rootAppId !== 'string' || !APP_ID.test(rootAppId) || walletAddress !== wallet || path !== recordPath(kind, rootAppId, id)) {
|
|
10877
|
+
throw new VenueEscrowError('venue_grant_unavailable', 'Bounded Auth answered with a grant that does not match this write.');
|
|
10878
|
+
}
|
|
10879
|
+
return { grant, venueAppId, rootAppId, path };
|
|
10880
|
+
}
|
|
10881
|
+
/* ------------------------------- reading -------------------------------- */
|
|
10882
|
+
/**
|
|
10883
|
+
* Read one venue escrow from the public record: its terms and its state (`pending` until the
|
|
10884
|
+
* venue's hook completes, `failed` when it did not). Resolves null when no such escrow exists.
|
|
10885
|
+
* No sign-in is needed.
|
|
10886
|
+
*/
|
|
10887
|
+
async function getVenueEscrow(input) {
|
|
10888
|
+
var _a;
|
|
10889
|
+
const { rootAppId, escrowId } = input !== null && input !== void 0 ? input : {};
|
|
10890
|
+
if (!APP_ID.test(String(rootAppId)) || !RECORD_ID.test(String(escrowId))) {
|
|
10891
|
+
throw new VenueEscrowError('invalid_input', 'getVenueEscrow needs a rootAppId (24 hex characters) and an escrowId (32 hex characters).');
|
|
10892
|
+
}
|
|
10893
|
+
const base = (_a = (await getConfig()).devApiUrl) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '');
|
|
10894
|
+
if (!base)
|
|
10895
|
+
throw new VenueEscrowError('read_unavailable', 'The Bounded developer API is not configured. Pass a Bounded network to init().');
|
|
10896
|
+
let response;
|
|
10897
|
+
try {
|
|
10898
|
+
// The 200 is cacheable for a minute, and a funding is polled through `pending`.
|
|
10899
|
+
response = await fetch(`${base}/public/oapp/${rootAppId}/escrows/${escrowId}`, { cache: 'no-store' });
|
|
10900
|
+
}
|
|
10901
|
+
catch (cause) {
|
|
10902
|
+
throw new VenueEscrowError('read_unavailable', 'Could not reach the Bounded developer API.', { cause });
|
|
10903
|
+
}
|
|
10904
|
+
if (response.status === 404)
|
|
10905
|
+
return null;
|
|
10906
|
+
const body = (await response.json().catch(() => null));
|
|
10907
|
+
if (!response.ok || (body === null || body === void 0 ? void 0 : body.ok) !== true || !body.escrow || typeof body.escrow !== 'object') {
|
|
10908
|
+
throw new VenueEscrowError('read_unavailable', `The escrow could not be read (${response.status}).`);
|
|
10909
|
+
}
|
|
10910
|
+
return body.escrow;
|
|
10911
|
+
}
|
|
10912
|
+
/**
|
|
10913
|
+
* The overrides that point a receipt's reconciliation at the venue it was issued for and the RPC of
|
|
10914
|
+
* the chain it ran on (the same one the write was broadcast through), with no credential.
|
|
10915
|
+
*/
|
|
10916
|
+
async function venueReadOverrides(venueAppId, chain) {
|
|
10917
|
+
var _a;
|
|
10918
|
+
const config = await getConfig();
|
|
10919
|
+
const rpcUrl = (_a = resolveVenueRpcUrl(config, chain, defaultSolanaRpcUrl)) !== null && _a !== void 0 ? _a : config.rpcUrl;
|
|
10920
|
+
return { _config: Object.assign(Object.assign({}, config), { appId: venueAppId, rpcUrl }), _readOnly: true };
|
|
10921
|
+
}
|
|
10922
|
+
const TERM_KEYS = ['payee', 'amountUsdc', 'challengeSeconds', 'expiresAt', 'ref'];
|
|
10923
|
+
/** Every term the funder signed, which a row must carry in full to be this funding. */
|
|
10924
|
+
const ROW_KEYS = ['schemaVersion', 'funder', 'payee', 'amountUsdc', 'challengeSeconds', 'expiresAt', 'ref', 'at'];
|
|
10925
|
+
/** The first thing wrong with these terms, or null. Shapes only: the venue's rule owns every bound. */
|
|
10926
|
+
function termsProblem(terms, wallet) {
|
|
10927
|
+
if (!terms || typeof terms.payee !== 'string' || !ADDRESS.test(terms.payee))
|
|
10928
|
+
return 'payee must be a Solana address';
|
|
10929
|
+
if (terms.payee === wallet)
|
|
10930
|
+
return 'payee must not be the wallet that is funding';
|
|
10931
|
+
for (const key of ['amountUsdc', 'challengeSeconds', 'expiresAt']) {
|
|
10932
|
+
if (!isCount(terms[key]))
|
|
10933
|
+
return `${key} must be a non-negative integer`;
|
|
10934
|
+
}
|
|
10935
|
+
if (typeof terms.ref !== 'string' || terms.ref.length < 1 || terms.ref.length > 128)
|
|
10936
|
+
return 'ref must be 1 to 128 characters';
|
|
10937
|
+
return null;
|
|
10938
|
+
}
|
|
10939
|
+
function isSavedSubmission(value) {
|
|
10940
|
+
var _a, _b;
|
|
10941
|
+
const saved = value;
|
|
10942
|
+
return !!saved && saved.status === 'submitted' && typeof saved.appId === 'string' && typeof saved.chain === 'string'
|
|
10943
|
+
&& typeof saved.transactionId === 'string' && saved.transactionId.length > 0
|
|
10944
|
+
&& typeof saved.signedTransaction === 'string' && Array.isArray(saved.requestedDocuments)
|
|
10945
|
+
&& typeof ((_a = saved.fence) === null || _a === void 0 ? void 0 : _a.blockhash) === 'string' && Number.isSafeInteger((_b = saved.fence) === null || _b === void 0 ? void 0 : _b.lastValidBlockHeight);
|
|
10946
|
+
}
|
|
10947
|
+
function isPending(value, wallet) {
|
|
10948
|
+
const doc = value === null || value === void 0 ? void 0 : value.document;
|
|
10949
|
+
const sent = (value === null || value === void 0 ? void 0 : value.path) !== undefined;
|
|
10950
|
+
return RECORD_ID.test(String(value === null || value === void 0 ? void 0 : value.escrowId)) && Number.isSafeInteger(value.savedAtMs) && value.savedAtMs > 0
|
|
10951
|
+
&& !!doc && doc.schemaVersion === ESCROW_SCHEMA && doc.funder === wallet && termsProblem(doc, wallet) === null && isCount(doc.at)
|
|
10952
|
+
&& [doc.approvedAt, doc.approvedKind, doc.approvalId, doc.disputedAt, doc.disputedBy, doc.settledAt, doc.settledKind, doc.settledBy].every((field) => field === null)
|
|
10953
|
+
&& (!sent || (APP_ID.test(String(value.rootAppId)) && APP_ID.test(String(value.venueAppId)) && value.path === recordPath('escrow', value.rootAppId, value.escrowId)))
|
|
10954
|
+
&& (value.submission === undefined || isSavedSubmission(value.submission));
|
|
10955
|
+
}
|
|
10956
|
+
function store() {
|
|
10957
|
+
const storage = getPlatform().storage;
|
|
10958
|
+
// A volatile fallback would forget a funding that may have landed, and the next click would send a second one.
|
|
10959
|
+
if (isVolatileStorage(storage))
|
|
10960
|
+
throw new VenueEscrowError('storage_unavailable', 'Venue escrow funding needs durable browser storage.');
|
|
10961
|
+
return storage;
|
|
10962
|
+
}
|
|
10963
|
+
function loadPending(key, wallet) {
|
|
10964
|
+
const storage = store();
|
|
10965
|
+
try {
|
|
10966
|
+
const raw = storage.getItem(key);
|
|
10967
|
+
if (!raw)
|
|
10968
|
+
return null;
|
|
10969
|
+
const value = JSON.parse(raw);
|
|
10970
|
+
if (!isPending(value, wallet))
|
|
10971
|
+
throw new Error('malformed');
|
|
10972
|
+
return value;
|
|
10973
|
+
}
|
|
10974
|
+
catch (_a) {
|
|
10975
|
+
throw new VenueEscrowError('storage_unavailable', 'The saved escrow funding could not be read, so another one will not be started.');
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
function savePending(key, pending) {
|
|
10979
|
+
const storage = store();
|
|
10980
|
+
try {
|
|
10981
|
+
storage.setItem(key, JSON.stringify(pending));
|
|
10982
|
+
}
|
|
10983
|
+
catch (_a) {
|
|
10984
|
+
throw new VenueEscrowError('storage_unavailable', 'The escrow funding could not be saved, so it was not sent.');
|
|
10985
|
+
}
|
|
10986
|
+
}
|
|
10987
|
+
function clearPending(key, escrowId) {
|
|
10988
|
+
try {
|
|
10989
|
+
const raw = store().getItem(key);
|
|
10990
|
+
if (raw && JSON.parse(raw).escrowId === escrowId)
|
|
10991
|
+
store().removeItem(key);
|
|
10992
|
+
}
|
|
10993
|
+
catch ( /* unreadable: the next call refuses to fund over it */_a) { /* unreadable: the next call refuses to fund over it */ }
|
|
10994
|
+
}
|
|
10995
|
+
function rowVerdict(pending, row) {
|
|
10996
|
+
var _a, _b;
|
|
10997
|
+
if (!ROW_KEYS.every((key) => row[key] === pending.document[key]))
|
|
10998
|
+
return { state: 'taken' };
|
|
10999
|
+
if (row.state === 'failed')
|
|
11000
|
+
return { state: 'failed' };
|
|
11001
|
+
if (row.state === 'pending')
|
|
11002
|
+
return { state: 'pending' };
|
|
11003
|
+
return { state: 'completed', transactionId: (_b = (_a = pending.submission) === null || _a === void 0 ? void 0 : _a.transactionId) !== null && _b !== void 0 ? _b : null };
|
|
11004
|
+
}
|
|
11005
|
+
/** Settle a saved funding without sending anything: the public row first, then its signature, then its age. */
|
|
11006
|
+
async function checkPending(pending) {
|
|
11007
|
+
if (!pending.path)
|
|
11008
|
+
return { state: 'not_applied', reason: 'it was interrupted before the venue was asked.' };
|
|
11009
|
+
let row;
|
|
11010
|
+
try {
|
|
11011
|
+
row = await getVenueEscrow({ rootAppId: pending.rootAppId, escrowId: pending.escrowId });
|
|
11012
|
+
}
|
|
11013
|
+
catch (_a) {
|
|
11014
|
+
return { state: 'pending' };
|
|
11015
|
+
}
|
|
11016
|
+
if (row)
|
|
11017
|
+
return rowVerdict(pending, row);
|
|
11018
|
+
if (pending.submission) {
|
|
11019
|
+
try {
|
|
11020
|
+
const settled = await reconcileSetResult(pending.submission, { _overrides: await venueReadOverrides(pending.venueAppId, pending.submission.chain) });
|
|
11021
|
+
if (settled.status === 'confirmed')
|
|
11022
|
+
return { state: 'completed', transactionId: settled.transactionId };
|
|
11023
|
+
if (settled.status === 'expired_not_landed')
|
|
11024
|
+
return { state: 'not_applied', reason: 'it expired before it reached the chain.' };
|
|
11025
|
+
}
|
|
11026
|
+
catch (error) {
|
|
11027
|
+
if (writeNotAppliedPhase(error) !== null)
|
|
11028
|
+
return { state: 'not_applied', reason: 'the transaction failed onchain.' };
|
|
11029
|
+
}
|
|
11030
|
+
return { state: 'pending' };
|
|
11031
|
+
}
|
|
11032
|
+
return Date.now() - pending.savedAtMs >= ABANDONED_MS
|
|
11033
|
+
? { state: 'not_applied', reason: 'no transaction was ever recorded for it, and it can no longer land.' }
|
|
11034
|
+
: { state: 'pending' };
|
|
11035
|
+
}
|
|
11036
|
+
/** Poll the public row until it settles: a direct poofnet commit is not funded until the venue's hook stamps it. */
|
|
11037
|
+
async function awaitRow(pending) {
|
|
11038
|
+
for (const delay of RECEIPT_POLL_MS) {
|
|
11039
|
+
if (delay)
|
|
11040
|
+
await pause(delay);
|
|
11041
|
+
let row = null;
|
|
11042
|
+
try {
|
|
11043
|
+
row = await getVenueEscrow({ rootAppId: pending.rootAppId, escrowId: pending.escrowId });
|
|
11044
|
+
}
|
|
11045
|
+
catch ( /* keep polling */_a) { /* keep polling */ }
|
|
11046
|
+
const verdict = row ? rowVerdict(pending, row) : null;
|
|
11047
|
+
if (verdict && verdict.state !== 'pending')
|
|
11048
|
+
return verdict;
|
|
11049
|
+
}
|
|
11050
|
+
return { state: 'pending' };
|
|
11051
|
+
}
|
|
11052
|
+
/** The one place a funding ends: return its result, or release the saved record and throw why not. */
|
|
11053
|
+
function conclude(key, pending, verdict, terms) {
|
|
11054
|
+
const { escrowId } = pending;
|
|
11055
|
+
if (verdict.state === 'pending') {
|
|
11056
|
+
throw new VenueEscrowError('pending', 'The funding is not confirmed yet. Call fundVenueEscrow again with the same terms to check it; do not start another.', { escrowId });
|
|
11057
|
+
}
|
|
11058
|
+
clearPending(key, escrowId);
|
|
11059
|
+
switch (verdict.state) {
|
|
11060
|
+
case 'completed':
|
|
11061
|
+
if (terms && !TERM_KEYS.every((term) => pending.document[term] === terms[term])) {
|
|
11062
|
+
throw new VenueEscrowError('funded_other_terms', `An earlier, interrupted funding with different terms landed as escrow ${escrowId}. Check it with getVenueEscrow before funding again.`, { escrowId });
|
|
11063
|
+
}
|
|
11064
|
+
return { escrowId, rootAppId: pending.rootAppId, venueAppId: pending.venueAppId, transactionId: verdict.transactionId };
|
|
11065
|
+
case 'taken':
|
|
11066
|
+
throw new VenueEscrowError('taken', 'A different record already holds this escrow id, so this funding did not land.', { escrowId });
|
|
11067
|
+
case 'failed':
|
|
11068
|
+
throw new VenueEscrowError('failed', 'The venue accepted the funding but its hook failed, so nothing moved. Try again.', { escrowId });
|
|
11069
|
+
default:
|
|
11070
|
+
throw new VenueEscrowError('not_applied', `The funding did not go through: ${verdict.reason} nothing moved. Try again.`, { escrowId });
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
11073
|
+
/**
|
|
11074
|
+
* Read a thrown write: what the SDK, the wallet or the venue said, as a code. The venue's own code
|
|
11075
|
+
* is read before its status, because a 403 is not always its rule: a grant outside its scope
|
|
11076
|
+
* (`venue_grant_scope`), a forbidden precondition or a signer that does not match the signing
|
|
11077
|
+
* request are not, and only a decline (or the program's own rule rejection) is `rule_refused`.
|
|
11078
|
+
*/
|
|
11079
|
+
function mapWriteError(error) {
|
|
11080
|
+
var _a;
|
|
11081
|
+
if (error instanceof VenueEscrowError)
|
|
11082
|
+
return error;
|
|
11083
|
+
const e = (error !== null && error !== void 0 ? error : {});
|
|
11084
|
+
const text = typeof e.message === 'string' ? e.message : String(error);
|
|
11085
|
+
const code = e.code === 'chain_mismatch' || e.code === 'chain_not_configured' || e.code === 'sponsorship_refused' || e.code === 'sponsorship_unavailable' ? e.code
|
|
11086
|
+
: e.name === 'InsufficientBalanceError' ? 'insufficient_balance'
|
|
11087
|
+
: e.code === 'venue_grant_scope' || e.statusCode === 401 ? 'grant_rejected'
|
|
11088
|
+
: e.isBoundedDecline === true || e.boundedRuleRejection === true ? 'rule_refused'
|
|
11089
|
+
: e.statusCode === 429 ? 'rate_limited'
|
|
11090
|
+
: /^top_level_required/.test(text) ? 'top_level_required'
|
|
11091
|
+
: /popup blocked/i.test(text) ? 'popup_blocked'
|
|
11092
|
+
// The wallet's or the window's own words only: a venue answer that happens to say "denied" is not the user declining.
|
|
11093
|
+
: e.statusCode === undefined && /user rejected|rejected the request|declined|denied|cancel|window was closed/i.test(text) ? 'declined'
|
|
11094
|
+
: 'write_failed';
|
|
11095
|
+
const message = code === 'rule_refused' && typeof ((_a = e.decline) === null || _a === void 0 ? void 0 : _a.message) === 'string' ? e.decline.message
|
|
11096
|
+
: e.boundedRuleRejection === true ? 'The venue\'s onchain rules refused this write, usually because the approval took longer than the two minutes a write stays valid. Start again.'
|
|
11097
|
+
: text;
|
|
11098
|
+
return new VenueEscrowError(code, message, { cause: error });
|
|
11099
|
+
}
|
|
11100
|
+
/** What a write that returned, rather than threw, means for this funding. */
|
|
11101
|
+
async function settleResult(key, pending, result, terms) {
|
|
11102
|
+
var _a;
|
|
11103
|
+
let current = result;
|
|
11104
|
+
if (current.status === 'submitted') {
|
|
11105
|
+
try {
|
|
11106
|
+
current = await waitForSetResult(current, { _overrides: await venueReadOverrides(pending.venueAppId, current.chain), timeoutMs: 60000 });
|
|
11107
|
+
}
|
|
11108
|
+
catch (error) {
|
|
11109
|
+
if (writeNotAppliedPhase(error) !== null) {
|
|
11110
|
+
clearPending(key, pending.escrowId);
|
|
11111
|
+
throw mapWriteError(error);
|
|
11112
|
+
}
|
|
11113
|
+
}
|
|
11114
|
+
}
|
|
11115
|
+
switch (current.status) {
|
|
11116
|
+
case 'confirmed':
|
|
11117
|
+
return conclude(key, pending, { state: 'completed', transactionId: current.transactionId }, terms);
|
|
11118
|
+
case 'committed': {
|
|
11119
|
+
const verdict = await awaitRow(pending);
|
|
11120
|
+
return conclude(key, pending, verdict.state === 'completed' ? { state: 'completed', transactionId: (_a = current.transactionId) !== null && _a !== void 0 ? _a : verdict.transactionId } : verdict, terms);
|
|
11121
|
+
}
|
|
11122
|
+
case 'expired_not_landed':
|
|
11123
|
+
return conclude(key, pending, { state: 'not_applied', reason: 'it expired before it reached the chain.' }, terms);
|
|
11124
|
+
case 'signed':
|
|
11125
|
+
return conclude(key, pending, { state: 'not_applied', reason: 'it was signed but never sent.' }, terms);
|
|
11126
|
+
default:
|
|
11127
|
+
return conclude(key, pending, { state: 'pending' }, terms);
|
|
11128
|
+
}
|
|
11129
|
+
}
|
|
11130
|
+
async function fundOrCheck(key, wallet, terms) {
|
|
11131
|
+
const saved = loadPending(key, wallet);
|
|
11132
|
+
if (saved)
|
|
11133
|
+
return conclude(key, saved, await checkPending(saved), terms);
|
|
11134
|
+
let pending = {
|
|
11135
|
+
escrowId: newRecordId(),
|
|
11136
|
+
savedAtMs: Date.now(),
|
|
11137
|
+
document: {
|
|
11138
|
+
payee: terms.payee, amountUsdc: terms.amountUsdc, challengeSeconds: terms.challengeSeconds, expiresAt: terms.expiresAt, ref: terms.ref,
|
|
11139
|
+
schemaVersion: ESCROW_SCHEMA, funder: wallet, at: nowSeconds(),
|
|
11140
|
+
approvedAt: null, approvedKind: null, approvalId: null, disputedAt: null, disputedBy: null,
|
|
11141
|
+
settledAt: null, settledKind: null, settledBy: null,
|
|
11142
|
+
},
|
|
11143
|
+
};
|
|
11144
|
+
const { escrowId } = pending;
|
|
11145
|
+
savePending(key, pending); // before any network call: the id must survive a lost answer
|
|
11146
|
+
let minted;
|
|
11147
|
+
try {
|
|
11148
|
+
minted = await mintGrant('escrow', escrowId, wallet);
|
|
11149
|
+
// The venue's rule holds `at` within two minutes of the chain clock, and the venue bakes it into the
|
|
11150
|
+
// transaction it builds from this write, so it is taken as late as it can be: after the mint, in the save
|
|
11151
|
+
// that adds the venue path, right before the write. The id and document were already saved before any
|
|
11152
|
+
// network call, and nothing has reached the venue yet, so recovery is unchanged.
|
|
11153
|
+
pending = Object.assign(Object.assign({}, pending), { document: Object.assign(Object.assign({}, pending.document), { at: nowSeconds() }), rootAppId: minted.rootAppId, venueAppId: minted.venueAppId, path: minted.path });
|
|
11154
|
+
savePending(key, pending);
|
|
11155
|
+
}
|
|
11156
|
+
catch (error) {
|
|
11157
|
+
clearPending(key, escrowId); // the venue was never asked, so there is nothing to settle
|
|
11158
|
+
throw error;
|
|
11159
|
+
}
|
|
11160
|
+
let result;
|
|
11161
|
+
try {
|
|
11162
|
+
result = await setWithVenueGrant(minted.path, pending.document, {
|
|
11163
|
+
venueAppId: minted.venueAppId,
|
|
11164
|
+
grant: minted.grant,
|
|
11165
|
+
commitGrant: async () => (await mintGrant('escrow', escrowId, wallet)).grant,
|
|
11166
|
+
resolveRpcUrl: defaultSolanaRpcUrl,
|
|
11167
|
+
onBroadcast: (submission) => {
|
|
11168
|
+
const next = Object.assign(Object.assign({}, pending), { submission });
|
|
11169
|
+
savePending(key, next);
|
|
11170
|
+
pending = next;
|
|
11171
|
+
},
|
|
11172
|
+
});
|
|
11173
|
+
}
|
|
11174
|
+
catch (error) {
|
|
11175
|
+
if (writeNotAppliedPhase(error) !== null) {
|
|
11176
|
+
clearPending(key, escrowId);
|
|
11177
|
+
throw mapWriteError(error);
|
|
11178
|
+
}
|
|
11179
|
+
// Anything else may follow a commit or broadcast whose answer was lost: settle it by the public
|
|
11180
|
+
// row, then by the saved signature. Never send again.
|
|
11181
|
+
const verdict = await awaitRow(pending);
|
|
11182
|
+
return conclude(key, pending, verdict.state === 'pending' && pending.submission ? await checkPending(pending) : verdict, terms);
|
|
11183
|
+
}
|
|
11184
|
+
return settleResult(key, pending, result, terms);
|
|
11185
|
+
}
|
|
11186
|
+
/** Poll the escrow until it shows disputed: a direct poofnet commit is not a dispute until the venue's hook stamps it. */
|
|
11187
|
+
async function awaitDisputed(rootAppId, escrowId) {
|
|
11188
|
+
for (const delay of RECEIPT_POLL_MS) {
|
|
11189
|
+
if (delay)
|
|
11190
|
+
await pause(delay);
|
|
11191
|
+
const row = await getVenueEscrow({ rootAppId, escrowId }).catch(() => null);
|
|
11192
|
+
if (row && row.disputedAt !== null)
|
|
11193
|
+
return true;
|
|
11194
|
+
}
|
|
11195
|
+
return false;
|
|
11196
|
+
}
|
|
11197
|
+
async function dispute(wallet, escrowId, reason) {
|
|
11198
|
+
const disputeId = newRecordId();
|
|
11199
|
+
const minted = await mintGrant('dispute', disputeId, wallet);
|
|
11200
|
+
let result = null;
|
|
11201
|
+
try {
|
|
11202
|
+
result = await setWithVenueGrant(minted.path, { escrowId, reason, at: nowSeconds() }, {
|
|
11203
|
+
venueAppId: minted.venueAppId,
|
|
11204
|
+
grant: minted.grant,
|
|
11205
|
+
commitGrant: async () => (await mintGrant('dispute', disputeId, wallet)).grant,
|
|
11206
|
+
resolveRpcUrl: defaultSolanaRpcUrl,
|
|
11207
|
+
});
|
|
11208
|
+
if (result.status === 'submitted')
|
|
11209
|
+
result = await waitForSetResult(result, { _overrides: await venueReadOverrides(minted.venueAppId, result.chain), timeoutMs: 60000 });
|
|
11210
|
+
}
|
|
11211
|
+
catch (error) {
|
|
11212
|
+
if (writeNotAppliedPhase(error) !== null)
|
|
11213
|
+
throw mapWriteError(error);
|
|
11214
|
+
// Anything else may follow a commit or broadcast whose answer was lost: the escrow says.
|
|
11215
|
+
}
|
|
11216
|
+
const done = { disputeId, rootAppId: minted.rootAppId, venueAppId: minted.venueAppId };
|
|
11217
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 'confirmed')
|
|
11218
|
+
return Object.assign(Object.assign({}, done), { transactionId: result.transactionId });
|
|
11219
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 'expired_not_landed' || (result === null || result === void 0 ? void 0 : result.status) === 'signed') {
|
|
11220
|
+
throw new VenueEscrowError('not_applied', 'The dispute never reached the chain, so nothing was sent. Try again.', { escrowId });
|
|
11221
|
+
}
|
|
11222
|
+
if (await awaitDisputed(minted.rootAppId, escrowId)) {
|
|
11223
|
+
return Object.assign(Object.assign({}, done), { transactionId: (result === null || result === void 0 ? void 0 : result.status) === 'committed' ? result.transactionId : null });
|
|
11224
|
+
}
|
|
11225
|
+
throw new VenueEscrowError('pending', 'The dispute was sent but is not confirmed yet. Read the escrow with getVenueEscrow to see whether it shows as disputed.', { escrowId });
|
|
11226
|
+
}
|
|
11227
|
+
/* ----------------------------- one call at a time ------------------------------ */
|
|
11228
|
+
const flights = new Map();
|
|
11229
|
+
/**
|
|
11230
|
+
* Run `run` unless an identical call is already in flight (a double click shares its result) or a
|
|
11231
|
+
* different one is (refused, never silently answered with the other's result). A Turnkey session's
|
|
11232
|
+
* signer window is reserved here, synchronously, because this runs inside the click.
|
|
11233
|
+
*/
|
|
11234
|
+
function startFlow(key, fingerprint, needsSigner, run) {
|
|
11235
|
+
let reservation = null;
|
|
11236
|
+
try {
|
|
11237
|
+
const current = flights.get(key);
|
|
11238
|
+
if (current) {
|
|
11239
|
+
if (current.fingerprint === fingerprint)
|
|
11240
|
+
return current.promise;
|
|
11241
|
+
throw new VenueEscrowError('in_progress', 'A different venue escrow request is already in progress for this wallet.');
|
|
11242
|
+
}
|
|
11243
|
+
if (hasTurnkeyWallet() && needsSigner()) {
|
|
11244
|
+
reservation = reserveSignerPopup();
|
|
11245
|
+
if (!reservation)
|
|
11246
|
+
throw new VenueEscrowError('popup_blocked', 'Call this from a click handler so the wallet approval window can open, and allow popups for this site.');
|
|
11247
|
+
}
|
|
11248
|
+
// A flow that fails before the signer loads (a grant the venue refuses, a rule) leaves the reserved
|
|
11249
|
+
// window to explain itself with the error's words for a moment; one that signed has closed it already.
|
|
11250
|
+
const settled = (error) => {
|
|
11251
|
+
var _a;
|
|
11252
|
+
if (((_a = flights.get(key)) === null || _a === void 0 ? void 0 : _a.promise) === promise)
|
|
11253
|
+
flights.delete(key);
|
|
11254
|
+
reservation === null || reservation === void 0 ? void 0 : reservation.release(error instanceof Error ? error.message : undefined);
|
|
11255
|
+
};
|
|
11256
|
+
const promise = run().then((value) => { settled(); return value; }, (error) => { settled(error); throw error; });
|
|
11257
|
+
flights.set(key, { fingerprint, promise });
|
|
11258
|
+
return promise;
|
|
11259
|
+
}
|
|
11260
|
+
catch (error) {
|
|
11261
|
+
reservation === null || reservation === void 0 ? void 0 : reservation.release(error instanceof Error ? error.message : undefined);
|
|
11262
|
+
return Promise.reject(error);
|
|
11263
|
+
}
|
|
11264
|
+
}
|
|
11265
|
+
/** One tab funds at a time: the saved record is shared by every tab of this browser. */
|
|
11266
|
+
function withLock(key, run) {
|
|
11267
|
+
var _a;
|
|
11268
|
+
const locks = (_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.locks;
|
|
11269
|
+
if (!(locks === null || locks === void 0 ? void 0 : locks.request))
|
|
11270
|
+
return Promise.reject(new VenueEscrowError('lock_unavailable', 'Venue escrow funding needs the Web Locks API, so that one tab funds at a time.'));
|
|
11271
|
+
return locks.request(key, { ifAvailable: true }, async (lock) => {
|
|
11272
|
+
if (!lock)
|
|
11273
|
+
throw new VenueEscrowError('in_progress', 'This escrow is already being funded in another tab.');
|
|
11274
|
+
return run();
|
|
11275
|
+
});
|
|
11276
|
+
}
|
|
11277
|
+
/* --------------------------------- public API ---------------------------------- */
|
|
11278
|
+
/**
|
|
11279
|
+
* Lock USDC in the venue escrow for `terms.payee`, signed by the wallet signed in here.
|
|
11280
|
+
* MUST be called from a user click: an email (Turnkey) wallet's approval opens in its own Bounded
|
|
11281
|
+
* window, which a browser only allows inside the click (it is opened here, before anything is
|
|
11282
|
+
* awaited), and the approval shows the decoded terms. A Phantom or Wallet Standard wallet keeps
|
|
11283
|
+
* its own prompt. On a real-chain venue the transaction is broadcast on the VENUE's chain: through
|
|
11284
|
+
* the app's own `rpcUrl` when the app configured that same `chain` too (an `rpcUrl` alone does not
|
|
11285
|
+
* say which chain it speaks), otherwise through the platform's default RPC for the venue's chain, so
|
|
11286
|
+
* an app that never configured a chain just works. An app that configured a different `chain` is
|
|
11287
|
+
* refused (`chain_mismatch`) before anything is signed.
|
|
11288
|
+
*
|
|
11289
|
+
* Funding is made safe to retry: a funding that was interrupted (page closed, answer lost) is
|
|
11290
|
+
* saved before anything is sent, and the next call checks it instead of funding again, so pass
|
|
11291
|
+
* the same terms and it resolves to the same `escrowId`. Store `escrowId` on your own record.
|
|
11292
|
+
* See {@link VenueEscrowErrorCode} for the failures, and `pending` in particular.
|
|
11293
|
+
*/
|
|
11294
|
+
function fundVenueEscrow(terms) {
|
|
11295
|
+
try {
|
|
11296
|
+
const { appId, wallet } = sessionIdentity();
|
|
11297
|
+
const problem = termsProblem(terms, wallet);
|
|
11298
|
+
if (problem)
|
|
11299
|
+
throw new VenueEscrowError('invalid_input', `fundVenueEscrow: ${problem}.`);
|
|
11300
|
+
const checked = {
|
|
11301
|
+
payee: terms.payee, amountUsdc: terms.amountUsdc, challengeSeconds: terms.challengeSeconds, expiresAt: terms.expiresAt, ref: terms.ref,
|
|
11302
|
+
};
|
|
11303
|
+
const key = `${STORAGE_PREFIX}:${appId}:${wallet}`;
|
|
11304
|
+
return startFlow(key, JSON.stringify(checked), () => !loadPending(key, wallet), () => withLock(key, () => fundOrCheck(key, wallet, checked)));
|
|
11305
|
+
}
|
|
11306
|
+
catch (error) {
|
|
11307
|
+
return Promise.reject(error);
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
/**
|
|
11311
|
+
* Dispute a funded venue escrow as its funder (or payee), signed by the wallet signed in here.
|
|
11312
|
+
* MUST be called from a user click, for the same reason as {@link fundVenueEscrow}. `reason` is
|
|
11313
|
+
* 1 to 400 bytes of UTF-8: the venue allows 1000 characters, but a longer reason does not fit one
|
|
11314
|
+
* legacy-format transaction. Nothing is saved: a dispute that already landed makes the venue refuse
|
|
11315
|
+
* a second one (`rule_refused`).
|
|
11316
|
+
*/
|
|
11317
|
+
function disputeVenueEscrow(input) {
|
|
11318
|
+
try {
|
|
11319
|
+
const { appId, wallet } = sessionIdentity();
|
|
11320
|
+
const { escrowId, reason } = input !== null && input !== void 0 ? input : {};
|
|
11321
|
+
if (!RECORD_ID.test(String(escrowId)))
|
|
11322
|
+
throw new VenueEscrowError('invalid_input', 'disputeVenueEscrow: escrowId must be 32 hex characters.');
|
|
11323
|
+
if (typeof reason !== 'string' || reason.length < 1 || new TextEncoder().encode(reason).length > MAX_DISPUTE_REASON_BYTES) {
|
|
11324
|
+
throw new VenueEscrowError('invalid_input', `disputeVenueEscrow: reason must be 1 to ${MAX_DISPUTE_REASON_BYTES} bytes.`);
|
|
11325
|
+
}
|
|
11326
|
+
return startFlow(`dispute:${appId}:${wallet}:${escrowId}`, reason, () => true, () => dispute(wallet, escrowId, reason));
|
|
11327
|
+
}
|
|
11328
|
+
catch (error) {
|
|
11329
|
+
return Promise.reject(error);
|
|
11330
|
+
}
|
|
11331
|
+
}
|
|
11332
|
+
|
|
10598
11333
|
/**
|
|
10599
11334
|
* DS3-0414: does the transaction already carry a REAL signer signature? A Solana
|
|
10600
11335
|
* signature is computed over the compiled message, which INCLUDES recentBlockhash, so
|
|
@@ -10996,25 +11731,6 @@ class PrivyExpoProvider {
|
|
|
10996
11731
|
}
|
|
10997
11732
|
}
|
|
10998
11733
|
|
|
10999
|
-
/**
|
|
11000
|
-
* Default provider discovery: Phantom-first, then any Wallet-Standard
|
|
11001
|
-
* `window.solana`. Kept tiny + synchronous (no heavy imports) so it works inside
|
|
11002
|
-
* a user gesture and can be eagerly exported from the SDK entry.
|
|
11003
|
-
*/
|
|
11004
|
-
function defaultInjectedProvider() {
|
|
11005
|
-
var _a;
|
|
11006
|
-
if (typeof window === 'undefined')
|
|
11007
|
-
return null;
|
|
11008
|
-
const w = window;
|
|
11009
|
-
const phantom = (_a = w === null || w === void 0 ? void 0 : w.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
11010
|
-
if (phantom && typeof phantom.signMessage === 'function')
|
|
11011
|
-
return phantom;
|
|
11012
|
-
const generic = w === null || w === void 0 ? void 0 : w.solana;
|
|
11013
|
-
if (generic && typeof generic.signMessage === 'function')
|
|
11014
|
-
return generic;
|
|
11015
|
-
return null;
|
|
11016
|
-
}
|
|
11017
|
-
|
|
11018
11734
|
// injected-evm-wallet-types.ts — tiny, dependency-free surface for the EVM
|
|
11019
11735
|
// WALLET LOGIN path (BYO wallet via SIWE). The EVM twin of
|
|
11020
11736
|
// injected-wallet-types.ts: kept separate from injected-evm-wallet-provider.ts
|
|
@@ -11404,5 +12120,5 @@ function init(config = {}) {
|
|
|
11404
12120
|
return init$1(resolved);
|
|
11405
12121
|
}
|
|
11406
12122
|
|
|
11407
|
-
export {
|
|
11408
|
-
//# sourceMappingURL=index-
|
|
12123
|
+
export { onramp as $, logout as A, getAuthProvider as B, signInAnonymously as C, loginWithPrivy as D, loginWithWallet as E, ensureWalletLoginReady as F, ensureSharedWalletLoginReady as G, preloadWalletLogin as H, loginWithRedirect as I, completeLoginFromRedirect as J, getLoginReturnError as K, loginWithPopup as L, openBoundedWidget as M, ensureSignedIn as N, useAuth as O, useQuery as P, useBuildRun as Q, apps as R, SELECTED_WALLET_UNAVAILABLE_ERR as S, signSolanaMessageViaTurnkey as T, getOrCreateTurnkeyWallet as U, openTurnkeyKeyExport as V, WalletConfigError as W, getTurnkeyWalletSessionState as X, getTurnkeyWalletRecoveryUrl as Y, startTurnkeyEmailLogin as Z, getIdToken as _, signMessageWithTurnkey as a, onrampQuote as a0, onrampStatus as a1, fundVenueEscrow as a2, disputeVenueEscrow as a3, getVenueEscrow as a4, VenueEscrowError as a5, setPlatform as a6, OffchainAuthProvider as a7, PrivyExpoProvider as a8, requestEip6963Providers as a9, connectBroadcast as aa, connectServiceSession as ab, builds as ac, BuildsApiError as ad, mediaAgentTools as ae, NON_TERMINAL_STATES as af, TERMINAL_STATES as ag, RUN_STATES as ah, PARK_REASONS as ai, FAILURE_REASONS as aj, BUILD_REVIEW_CAPABILITY_SCHEMA as ak, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as al, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as am, signTransactionWithTurnkey as b, signTransactionV1WithTurnkey as c, signAndSubmitTransactionWithTurnkey as d, bs58 as e, defaultInjectedProvider as f, getPlatform as g, hasTurnkeyWallet as h, firstStandardWalletProvider as i, setAuthLoading as j, supportsWalletSignIn as k, bufferExports as l, setStoredAuthMethod as m, normalizeSolanaRpcUrl as n, confirmAndCheckTransaction as o, defaultInjectedEvmProvider as p, init as q, resolveSolanaRpcUrl as r, setCurrentUser as s, transactionHasSignature as t, getCurrentUser as u, onAuthStateChanged as v, onAuthLoadingChanged as w, getAuthLoading as x, waitForAuthReady as y, login as z };
|
|
12124
|
+
//# sourceMappingURL=index-C63ceG_z.mjs.map
|