@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
|
@@ -615,6 +615,37 @@ class OffchainAuthProvider {
|
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
+
const SELECTED_WALLET_UNAVAILABLE_ERR = 'The selected Solana wallet is unavailable. Select it again before continuing.';
|
|
619
|
+
/** Check login capabilities without connecting, signing, or loading a wallet SDK.
|
|
620
|
+
* This checks login only; it does not promise message or transaction signing. */
|
|
621
|
+
function supportsWalletSignIn(provider) {
|
|
622
|
+
if (typeof provider !== 'object' || provider === null)
|
|
623
|
+
return false;
|
|
624
|
+
const wallet = provider;
|
|
625
|
+
return typeof wallet.connect === 'function' && typeof wallet.signIn === 'function';
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Default provider discovery: Phantom-first, then any Wallet-Standard
|
|
629
|
+
* `window.solana`. Kept tiny + synchronous (no heavy imports) so it works inside
|
|
630
|
+
* a user gesture and can be eagerly exported from the SDK entry. This names the
|
|
631
|
+
* default wallet even when it cannot sign in: login must refuse that wallet,
|
|
632
|
+
* never silently replace it with a different identity. Custom pickers can use
|
|
633
|
+
* supportsWalletSignIn() to decide which providers to offer for login.
|
|
634
|
+
*/
|
|
635
|
+
function defaultInjectedProvider() {
|
|
636
|
+
var _a;
|
|
637
|
+
if (typeof window === 'undefined')
|
|
638
|
+
return null;
|
|
639
|
+
const w = window;
|
|
640
|
+
const phantom = (_a = w === null || w === void 0 ? void 0 : w.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
641
|
+
if (phantom && typeof phantom.signMessage === 'function')
|
|
642
|
+
return phantom;
|
|
643
|
+
const generic = w === null || w === void 0 ? void 0 : w.solana;
|
|
644
|
+
if (generic && typeof generic.signMessage === 'function')
|
|
645
|
+
return generic;
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
|
|
618
649
|
// issuer-logout-bounce.ts - commit the top-level navigation that ends the hosted
|
|
619
650
|
// issuer's browser session (the Better Auth cookie on auth.bounded.sh).
|
|
620
651
|
//
|
|
@@ -4554,6 +4585,108 @@ async function openHiddenSignerFrame(page) {
|
|
|
4554
4585
|
};
|
|
4555
4586
|
}
|
|
4556
4587
|
|
|
4588
|
+
// turnkey-signer-window.ts - the top-level Bounded signer window: opening it, and reserving it
|
|
4589
|
+
// inside a click for an approval that is reached only after awaits.
|
|
4590
|
+
//
|
|
4591
|
+
// A cross-app Turnkey signature (the capability says `transport: 'popup'`) cannot run in the
|
|
4592
|
+
// in-app frame, so it opens in its own window, and a browser opens a window only inside the
|
|
4593
|
+
// user's click. The click handler therefore calls `reserveSignerPopup()` first, before any
|
|
4594
|
+
// `await`, and the exchange in turnkey-signer-bridge.ts navigates that window to the signer later.
|
|
4595
|
+
//
|
|
4596
|
+
// Kept out of the bridge on purpose: the bridge is loaded lazily as a whole module, so a bundler
|
|
4597
|
+
// keeps every one of its exports in every app that signs, while this reservation is needed only
|
|
4598
|
+
// by an app that funds an escrow.
|
|
4599
|
+
function openSignerWindow(url, w = 420, h = 620) {
|
|
4600
|
+
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
4601
|
+
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4602
|
+
return window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4603
|
+
}
|
|
4604
|
+
// One slot: the next popup exchange takes it, and a new reservation replaces an unused earlier one.
|
|
4605
|
+
// A property of a const rather than a reassigned `let`, which a bundler would keep every assignment
|
|
4606
|
+
// to, dragging the reservation into apps that never reserve.
|
|
4607
|
+
const reserved = { popup: null };
|
|
4608
|
+
/** Write `text` into the window; false when the window is not ours to write (it still works: it is navigated to the signer next). */
|
|
4609
|
+
function paint(popup, text) {
|
|
4610
|
+
try {
|
|
4611
|
+
const { document: doc } = popup;
|
|
4612
|
+
doc.title = 'Bounded';
|
|
4613
|
+
doc.documentElement.style.colorScheme = 'light dark';
|
|
4614
|
+
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';
|
|
4615
|
+
doc.body.textContent = text;
|
|
4616
|
+
return true;
|
|
4617
|
+
}
|
|
4618
|
+
catch (_a) {
|
|
4619
|
+
return false;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
/**
|
|
4623
|
+
* Open the signer window NOW, synchronously, showing a neutral "Preparing your approval..."
|
|
4624
|
+
* page (a blank document with local text, no remote content). Call it from a user gesture,
|
|
4625
|
+
* before any `await`, and `release()` it when the flow ends. A cross-app Turnkey signature
|
|
4626
|
+
* then runs in this window instead of needing a fresh gesture.
|
|
4627
|
+
* Returns null when there is no DOM or the browser blocked the window.
|
|
4628
|
+
*/
|
|
4629
|
+
function reserveSignerPopup() {
|
|
4630
|
+
var _a;
|
|
4631
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
4632
|
+
return null;
|
|
4633
|
+
try {
|
|
4634
|
+
(_a = reserved.popup) === null || _a === void 0 ? void 0 : _a.close();
|
|
4635
|
+
}
|
|
4636
|
+
catch ( /* already closed */_b) { /* already closed */ }
|
|
4637
|
+
let popup = null;
|
|
4638
|
+
try {
|
|
4639
|
+
popup = openSignerWindow('');
|
|
4640
|
+
}
|
|
4641
|
+
catch ( /* a sandboxed page may throw instead of returning null */_c) { /* a sandboxed page may throw instead of returning null */ }
|
|
4642
|
+
reserved.popup = popup;
|
|
4643
|
+
if (!popup)
|
|
4644
|
+
return null;
|
|
4645
|
+
paint(popup, 'Preparing your approval...');
|
|
4646
|
+
return {
|
|
4647
|
+
release(message) {
|
|
4648
|
+
const unused = reserved.popup === popup;
|
|
4649
|
+
if (unused)
|
|
4650
|
+
reserved.popup = null;
|
|
4651
|
+
try {
|
|
4652
|
+
if (!message || !unused || popup.closed)
|
|
4653
|
+
return popup.close();
|
|
4654
|
+
const text = message.slice(0, 160);
|
|
4655
|
+
if (!paint(popup, text))
|
|
4656
|
+
return popup.close(); // not a window of ours to write on, so no message to linger on
|
|
4657
|
+
// Close it only while it still shows this text: a window that was reserved again or sent on to
|
|
4658
|
+
// the signer in the meantime is no longer ours to close.
|
|
4659
|
+
setTimeout(() => { try {
|
|
4660
|
+
if (popup.document.body.textContent === text)
|
|
4661
|
+
popup.close();
|
|
4662
|
+
}
|
|
4663
|
+
catch ( /* sent on to the signer */_a) { /* sent on to the signer */ } }, 1500);
|
|
4664
|
+
}
|
|
4665
|
+
catch ( /* already closed */_a) { /* already closed */ }
|
|
4666
|
+
},
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4669
|
+
/** Hand the reserved window, if one is held, to an exchange: navigated to the signer page at `url`. */
|
|
4670
|
+
function takeReservedPopup(url) {
|
|
4671
|
+
const popup = reserved.popup;
|
|
4672
|
+
reserved.popup = null;
|
|
4673
|
+
if (!popup)
|
|
4674
|
+
return null;
|
|
4675
|
+
if (popup.closed)
|
|
4676
|
+
throw new Error('Wallet approval window was closed before it completed.');
|
|
4677
|
+
try {
|
|
4678
|
+
popup.location.replace(url);
|
|
4679
|
+
}
|
|
4680
|
+
catch (_a) {
|
|
4681
|
+
try {
|
|
4682
|
+
popup.close();
|
|
4683
|
+
}
|
|
4684
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
4685
|
+
throw new Error('Wallet approval window could not open the signer - retry from a click.');
|
|
4686
|
+
}
|
|
4687
|
+
return popup;
|
|
4688
|
+
}
|
|
4689
|
+
|
|
4557
4690
|
// turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
|
|
4558
4691
|
// kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
|
|
4559
4692
|
//
|
|
@@ -4565,9 +4698,10 @@ async function openHiddenSignerFrame(page) {
|
|
|
4565
4698
|
// so a regression here (a dropped capability, a wrong route, a swallowed error) is
|
|
4566
4699
|
// caught without a browser.
|
|
4567
4700
|
// POST the Bounded id token + payload hash to the issuer's capability mint route and
|
|
4568
|
-
// return the issuer-signed capability for exactly these bytes
|
|
4569
|
-
// is proven with the id token in the
|
|
4570
|
-
// would not carry). Throws with the same
|
|
4701
|
+
// return the issuer-signed capability for exactly these bytes, with the transport the
|
|
4702
|
+
// issuer requires for them. Credential-free: identity is proven with the id token in the
|
|
4703
|
+
// body (not an ambient cookie the cross-origin fetch would not carry). Throws with the same
|
|
4704
|
+
// stable messages the bridge relied on inline.
|
|
4571
4705
|
async function requestTurnkeySigningCapability(opts) {
|
|
4572
4706
|
const base = opts.baseUrl.replace(/\/$/, '');
|
|
4573
4707
|
let res;
|
|
@@ -4597,7 +4731,7 @@ async function requestTurnkeySigningCapability(opts) {
|
|
|
4597
4731
|
if (!d || typeof d.capability !== 'string' || !d.capability) {
|
|
4598
4732
|
throw new Error('Turnkey signing capability was malformed.');
|
|
4599
4733
|
}
|
|
4600
|
-
return d.capability;
|
|
4734
|
+
return d.transport === 'popup' ? { capability: d.capability, transport: 'popup' } : { capability: d.capability };
|
|
4601
4735
|
}
|
|
4602
4736
|
// Assemble the `bounded:turnkey:sign` postMessage request, carrying the issuer-signed
|
|
4603
4737
|
// capability for the same bytes. Without the capability the hosted signer refuses
|
|
@@ -4688,12 +4822,16 @@ function hasDOM$3() {
|
|
|
4688
4822
|
// when we cannot embed (no DOM, or the frame never mounts/loads). Nothing in the
|
|
4689
4823
|
// email-session flow needs a popup by preference: the code entry and the approval
|
|
4690
4824
|
// click both run fine inside a cross-origin iframe on every engine (the old
|
|
4691
|
-
// Safari popup preference existed only for in-iframe WebAuthn CREATE).
|
|
4825
|
+
// Safari popup preference existed only for in-iframe WebAuthn CREATE). The one
|
|
4826
|
+
// exception is a signature the issuer marks `transport: 'popup'` (a cross-app
|
|
4827
|
+
// write): the hosted signer refuses it inside any frame, so it never touches the widget.
|
|
4692
4828
|
async function deliver(url, origin, ex) {
|
|
4693
4829
|
var _a, _b;
|
|
4694
4830
|
const popup = () => runPopup(url, origin, ex);
|
|
4695
4831
|
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4696
|
-
const result =
|
|
4832
|
+
const result = ex.transport === 'popup'
|
|
4833
|
+
? await popup()
|
|
4834
|
+
: await runWidget(url, origin, { request: ex.request, onResult: ex.onResult, context: ex.context, assertCurrent: ex.assertCurrent }, popup);
|
|
4697
4835
|
(_b = ex.assertCurrent) === null || _b === void 0 ? void 0 : _b.call(ex);
|
|
4698
4836
|
return result;
|
|
4699
4837
|
}
|
|
@@ -4765,14 +4903,12 @@ function activeTurnkeyAddress() {
|
|
|
4765
4903
|
: null;
|
|
4766
4904
|
}
|
|
4767
4905
|
let requestSeq = 0;
|
|
4768
|
-
// Shared popup driver: open the signer page
|
|
4769
|
-
// resolve when `onResult` accepts a result message. Origin +
|
|
4906
|
+
// Shared popup driver: open the signer page (in the reserved window when one is held), do the
|
|
4907
|
+
// ready/postMessage handshake, and resolve when `onResult` accepts a result message. Origin +
|
|
4908
|
+
// source are pinned.
|
|
4770
4909
|
async function runPopup(url, signerOrigin, ex) {
|
|
4771
|
-
var _a
|
|
4772
|
-
const
|
|
4773
|
-
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
4774
|
-
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4775
|
-
const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4910
|
+
var _a;
|
|
4911
|
+
const popup = (_a = takeReservedPopup(url)) !== null && _a !== void 0 ? _a : openSignerWindow(url, ex.width, ex.height);
|
|
4776
4912
|
if (!popup) {
|
|
4777
4913
|
throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the wallet approval window can open.');
|
|
4778
4914
|
}
|
|
@@ -5022,7 +5158,7 @@ async function signSolanaMessageViaTurnkey(message) {
|
|
|
5022
5158
|
const messageHex = toHex(message);
|
|
5023
5159
|
// AS-02: obtain the issuer-signed capability for THESE exact bytes first; the signer will
|
|
5024
5160
|
// refuse (capability_required) without it. Fetched per signature (each is single-payload).
|
|
5025
|
-
const capability = await fetchSigningCapability(messageHex, context);
|
|
5161
|
+
const { capability, transport } = await fetchSigningCapability(messageHex, context);
|
|
5026
5162
|
// Inline (authMode:"turnkey") sessions plant no Better Auth cookie on the issuer, so the
|
|
5027
5163
|
// signer's same-origin status/create fetches need a bearer session proof: attach the
|
|
5028
5164
|
// createWallet action authorization (it covers the sign flow's get-or-create too). Minted
|
|
@@ -5031,11 +5167,20 @@ async function signSolanaMessageViaTurnkey(message) {
|
|
|
5031
5167
|
return await deliver(url, origin, {
|
|
5032
5168
|
context: context.key, assertCurrent: context.assertCurrent,
|
|
5033
5169
|
request: buildTurnkeySignRequest({ requestId, messageHex, capability, authorization }),
|
|
5170
|
+
transport,
|
|
5034
5171
|
onResult: (d) => {
|
|
5035
5172
|
if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
|
|
5036
|
-
|
|
5173
|
+
// A top-level popup keeps its signing session in the signer origin's own storage, which the
|
|
5174
|
+
// warm in-app frame (a partitioned context) cannot see, so it says nothing about that frame.
|
|
5175
|
+
if (transport !== 'popup')
|
|
5176
|
+
setWalletSessionState(context.key, { status: 'ready', expiresAt: Number(d.signingSessionExpiresAt) });
|
|
5037
5177
|
return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
5038
5178
|
}
|
|
5179
|
+
// The hosted signer refuses a cross-app signature inside a frame. Never retry it from here:
|
|
5180
|
+
// the top-level window needs the user's own click.
|
|
5181
|
+
if (d.error === 'top_level_required') {
|
|
5182
|
+
return { error: 'top_level_required: an approval that writes to another app must open in its own window. Start it from a click handler.' };
|
|
5183
|
+
}
|
|
5039
5184
|
return { error: String(d.error || 'Turnkey signing failed') };
|
|
5040
5185
|
},
|
|
5041
5186
|
});
|
|
@@ -6351,7 +6496,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6351
6496
|
// manager as legacy email sessions. Human login should be started
|
|
6352
6497
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
6353
6498
|
// logout surface for those sessions.
|
|
6354
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-
|
|
6499
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-CPHrcy_b.js'); })).EmailAuthProvider();
|
|
6355
6500
|
break;
|
|
6356
6501
|
case "phantom": {
|
|
6357
6502
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -6393,7 +6538,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6393
6538
|
// (bounded-login-widget.ts), so registration never counts as
|
|
6394
6539
|
// evidence the user has a wallet.
|
|
6395
6540
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
6396
|
-
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6541
|
+
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-BXFicWTx.js'); });
|
|
6397
6542
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
6398
6543
|
break;
|
|
6399
6544
|
}
|
|
@@ -6406,7 +6551,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6406
6551
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
6407
6552
|
// to point at a specific wallet or set chainId/domain.
|
|
6408
6553
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
6409
|
-
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-
|
|
6554
|
+
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-D1JA3cL4.js'); });
|
|
6410
6555
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
6411
6556
|
break;
|
|
6412
6557
|
}
|
|
@@ -6431,7 +6576,7 @@ async function getAuthProvider(config, fromStorage = false) {
|
|
|
6431
6576
|
break;
|
|
6432
6577
|
case "guest":
|
|
6433
6578
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
6434
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-
|
|
6579
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-d6dk97x6.js'); })).GuestAuthProvider();
|
|
6435
6580
|
break;
|
|
6436
6581
|
}
|
|
6437
6582
|
if (!currentAuthProvider) {
|
|
@@ -6493,7 +6638,7 @@ async function signInAnonymously() {
|
|
|
6493
6638
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
6494
6639
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
6495
6640
|
// so bundle-splitting can see it.
|
|
6496
|
-
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-
|
|
6641
|
+
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-d6dk97x6.js'); })).GuestAuthProvider();
|
|
6497
6642
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
6498
6643
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
6499
6644
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6664,7 +6809,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6664
6809
|
if (!sharedWalletReady) {
|
|
6665
6810
|
sharedWalletReady = (async () => {
|
|
6666
6811
|
await core.getConfig();
|
|
6667
|
-
await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6812
|
+
await Promise.resolve().then(function () { return require('./injected-wallet-provider-BXFicWTx.js'); });
|
|
6668
6813
|
})().catch((error) => {
|
|
6669
6814
|
sharedWalletReady = null;
|
|
6670
6815
|
throw error;
|
|
@@ -6746,10 +6891,12 @@ async function loginWithWallet(options) {
|
|
|
6746
6891
|
// one that yields nothing and then a wallet prepares the mobile registry
|
|
6747
6892
|
// after the tap for a wallet it never uses.
|
|
6748
6893
|
const pinnedProvider = (_b = (_a = injectedConfig.getProvider) === null || _a === void 0 ? void 0 : _a.call(injectedConfig)) !== null && _b !== void 0 ? _b : null;
|
|
6894
|
+
if (injectedConfig.getProvider && !pinnedProvider)
|
|
6895
|
+
throw new Error(SELECTED_WALLET_UNAVAILABLE_ERR);
|
|
6749
6896
|
const attemptConfig = Object.assign(Object.assign({}, injectedConfig), {
|
|
6750
|
-
//
|
|
6751
|
-
//
|
|
6752
|
-
getProvider: () => pinnedProvider });
|
|
6897
|
+
// An explicit choice stays pinned. With no getter, retain default
|
|
6898
|
+
// discovery rather than manufacturing an unavailable explicit choice.
|
|
6899
|
+
getProvider: injectedConfig.getProvider ? () => pinnedProvider : undefined });
|
|
6753
6900
|
// Idempotent: a caller that prepared the lane (the widget, or an app that
|
|
6754
6901
|
// awaited ensureWalletLoginReady before enabling its button) has already
|
|
6755
6902
|
// paid for this, so nothing activation-sensitive happens after the tap. A
|
|
@@ -6758,16 +6905,16 @@ async function loginWithWallet(options) {
|
|
|
6758
6905
|
//
|
|
6759
6906
|
// "Pinned" is about the EFFECTIVE resolution, not whether a getter was
|
|
6760
6907
|
// passed: a configured walletLogin.getProvider pins just as much as a
|
|
6761
|
-
// per-call one
|
|
6762
|
-
//
|
|
6763
|
-
//
|
|
6908
|
+
// per-call one. An unavailable explicit choice was refused above. With a
|
|
6909
|
+
// real wallet in hand only the shared half is needed - re-attempting a
|
|
6910
|
+
// failed mobile registration would be
|
|
6764
6911
|
// exactly the work-after-the-tap this exists to avoid, for a wallet this
|
|
6765
6912
|
// login is not even using.
|
|
6766
6913
|
if (pinnedProvider)
|
|
6767
6914
|
await ensureSharedWalletLoginReady();
|
|
6768
6915
|
else
|
|
6769
6916
|
await ensureWalletLoginReady();
|
|
6770
|
-
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6917
|
+
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-BXFicWTx.js'); });
|
|
6771
6918
|
// Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
|
|
6772
6919
|
// set of methods, so cleanup below must speak to the provider that actually
|
|
6773
6920
|
// holds the config rather than to whatever is wrapping it.
|
|
@@ -7595,15 +7742,28 @@ function hasFeature(wallet, name) {
|
|
|
7595
7742
|
function supportsSolana(wallet) {
|
|
7596
7743
|
return wallet.chains.some((c) => c.startsWith("solana:"));
|
|
7597
7744
|
}
|
|
7598
|
-
/** A wallet
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
* refused with the reason spelled out rather than silently missing from the list.
|
|
7602
|
-
* Transaction features are optional and checked lazily at call time. */
|
|
7603
|
-
function isLoginCapable(wallet) {
|
|
7745
|
+
/** A Solana wallet the SDK can connect and use for message signing. */
|
|
7746
|
+
function isSolanaWallet(wallet) {
|
|
7747
|
+
var _a, _b;
|
|
7604
7748
|
return (supportsSolana(wallet) &&
|
|
7605
|
-
|
|
7606
|
-
|
|
7749
|
+
typeof ((_a = feature(wallet, StandardConnect)) === null || _a === void 0 ? void 0 : _a.connect) === "function" &&
|
|
7750
|
+
typeof ((_b = feature(wallet, SolanaSignMessage)) === null || _b === void 0 ? void 0 : _b.signMessage) === "function");
|
|
7751
|
+
}
|
|
7752
|
+
/** Login choices must support the same SIWS operation login() requires.
|
|
7753
|
+
* Transaction features remain optional and are checked at call time. */
|
|
7754
|
+
function isLoginCapable(wallet) {
|
|
7755
|
+
var _a, _b;
|
|
7756
|
+
return isSolanaWallet(wallet) && supportsWalletSignIn({
|
|
7757
|
+
connect: (_a = feature(wallet, StandardConnect)) === null || _a === void 0 ? void 0 : _a.connect,
|
|
7758
|
+
signIn: (_b = feature(wallet, SolanaSignIn)) === null || _b === void 0 ? void 0 : _b.signIn,
|
|
7759
|
+
});
|
|
7760
|
+
}
|
|
7761
|
+
/** Distinguish unsupported installed wallets from an empty registry in the UI. */
|
|
7762
|
+
function hasUnsupportedStandardWallets(configuredNetwork) {
|
|
7763
|
+
if (typeof window === "undefined")
|
|
7764
|
+
return false;
|
|
7765
|
+
const chain = configuredNetwork ? solanaChainOrNull(configuredNetwork) : null;
|
|
7766
|
+
return getWallets().get().some((wallet) => (isSolanaWallet(wallet) && (!chain || wallet.chains.includes(chain)) && !isLoginCapable(wallet)));
|
|
7607
7767
|
}
|
|
7608
7768
|
/** The Wallet-Standard chain id for a configured network, or null when it is
|
|
7609
7769
|
* absent/unsupported. Ordering must never throw - failing closed on an
|
|
@@ -7632,6 +7792,7 @@ function feature(wallet, name) {
|
|
|
7632
7792
|
* an unconfigured network fails closed at sign time rather than defaulting.
|
|
7633
7793
|
*/
|
|
7634
7794
|
function standardWalletToInjectedProvider(wallet, configuredNetwork) {
|
|
7795
|
+
var _a;
|
|
7635
7796
|
// The adapter holds NO account of its own: `wallet.accounts` is the Wallet
|
|
7636
7797
|
// Standard's live authorized-account view, kept in sync by the wallet itself
|
|
7637
7798
|
// (it emits standard:events 'change' when it moves). Reading it on every
|
|
@@ -7731,12 +7892,12 @@ function standardWalletToInjectedProvider(wallet, configuredNetwork) {
|
|
|
7731
7892
|
const disconnect = feature(wallet, StandardDisconnect);
|
|
7732
7893
|
if (disconnect)
|
|
7733
7894
|
await disconnect.disconnect();
|
|
7734
|
-
} }, (
|
|
7895
|
+
} }, (typeof ((_a = feature(wallet, SolanaSignIn)) === null || _a === void 0 ? void 0 : _a.signIn) === "function"
|
|
7735
7896
|
? {
|
|
7736
7897
|
async signIn(input) {
|
|
7737
7898
|
var _a;
|
|
7738
7899
|
const sign = feature(wallet, SolanaSignIn);
|
|
7739
|
-
if (
|
|
7900
|
+
if (typeof (sign === null || sign === void 0 ? void 0 : sign.signIn) !== "function")
|
|
7740
7901
|
throw new Error("Wallet does not support solana:signIn");
|
|
7741
7902
|
const open = currentAccount();
|
|
7742
7903
|
if (open && !accountSupports(open, SolanaSignIn))
|
|
@@ -8284,14 +8445,12 @@ function walletGlyph() {
|
|
|
8284
8445
|
return svg;
|
|
8285
8446
|
}
|
|
8286
8447
|
function isInjectedProvider(v) {
|
|
8287
|
-
return (
|
|
8288
|
-
v !== null &&
|
|
8289
|
-
typeof v.signMessage === "function");
|
|
8448
|
+
return supportsWalletSignIn(v);
|
|
8290
8449
|
}
|
|
8291
8450
|
// Enumerate native wallets via the Solana Wallet Standard registry (NOT a
|
|
8292
|
-
// hardcoded name list) -
|
|
8293
|
-
// real name + icon - and ALSO an older wallet that never
|
|
8294
|
-
// exposes `window.solana`.
|
|
8451
|
+
// hardcoded name list) - wallets that support Bounded sign-in show up with
|
|
8452
|
+
// their real name + icon - and ALSO an eligible older wallet that never
|
|
8453
|
+
// registers and only exposes `window.solana`.
|
|
8295
8454
|
//
|
|
8296
8455
|
// The legacy `window.solana` probe fills in for a wallet too old to register
|
|
8297
8456
|
// itself. The rule it has always encoded is "no BROWSER wallet registered", and
|
|
@@ -8326,7 +8485,11 @@ const BUSY_MSG = "A sign-in is already in progress - finish or cancel it first."
|
|
|
8326
8485
|
// no wallet in this browser). A phone browser never sees installed wallet apps
|
|
8327
8486
|
// as injected providers, so the honest advice there is a different browser,
|
|
8328
8487
|
// not "install a wallet".
|
|
8329
|
-
function noLaneCopy(mobile) {
|
|
8488
|
+
function noLaneCopy(mobile, unsupportedWallets) {
|
|
8489
|
+
if (unsupportedWallets) {
|
|
8490
|
+
return "The wallets detected in this browser cannot sign in to Bounded. " +
|
|
8491
|
+
"Use a wallet that supports Sign In With Solana, then reload this page.";
|
|
8492
|
+
}
|
|
8330
8493
|
return mobile
|
|
8331
8494
|
? "No wallet is available in this browser. Open this page in your wallet app's " +
|
|
8332
8495
|
"browser, or on a desktop browser with a wallet extension, then try again."
|
|
@@ -8524,6 +8687,9 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8524
8687
|
// button with no wallet behind it.
|
|
8525
8688
|
if (wallets.length === 0)
|
|
8526
8689
|
walletEnabled = false;
|
|
8690
|
+
const defaultWallet = defaultInjectedProvider();
|
|
8691
|
+
const unsupportedWallets = !lane.hardOff && lane.requested !== false && (hasUnsupportedStandardWallets(network) ||
|
|
8692
|
+
(defaultWallet !== null && !supportsWalletSignIn(defaultWallet)));
|
|
8527
8693
|
const authMode = (_b = opts.authMode) !== null && _b !== void 0 ? _b : authApi.resolveAuthMode(initCfg);
|
|
8528
8694
|
// Widget text: per-call options win, then init() loginWidget config, then the
|
|
8529
8695
|
// defaults ("Sign in" / the Bounded subline; "" suppresses the subline).
|
|
@@ -8902,14 +9068,19 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8902
9068
|
// screen, and name the usual cause once in the console, where the
|
|
8903
9069
|
// developer's own preview run will show it.
|
|
8904
9070
|
if (!hasEmail && social.length === 0 && !walletEnabled) {
|
|
8905
|
-
card.appendChild(el("p", "sub", noLaneCopy(detectMobile())));
|
|
9071
|
+
card.appendChild(el("p", "sub", noLaneCopy(detectMobile(), unsupportedWallets)));
|
|
8906
9072
|
console.warn("[Bounded] openBoundedWidget has no sign-in lane to show: the app asked for " +
|
|
8907
|
-
"no email or social method and
|
|
8908
|
-
(
|
|
9073
|
+
"no email or social method and " +
|
|
9074
|
+
(unsupportedWallets
|
|
9075
|
+
? "the detected wallets do not support Sign In With Solana. "
|
|
9076
|
+
: "no wallet is available in this browser. ") +
|
|
9077
|
+
(offerMobile && !unsupportedWallets
|
|
8909
9078
|
? "On Android the Solana Mobile wallet is offered only when the app installs " +
|
|
8910
9079
|
"'@solana-mobile/wallet-standard-mobile' (see the warning above if registration failed); "
|
|
8911
9080
|
: "") +
|
|
8912
|
-
|
|
9081
|
+
(unsupportedWallets
|
|
9082
|
+
? "Add another sign-in method, or use a wallet that supports Sign In With Solana."
|
|
9083
|
+
: "Add a method, or make sure wallet login is enabled and the mobile adapter is installed."));
|
|
8913
9084
|
}
|
|
8914
9085
|
card.appendChild(status);
|
|
8915
9086
|
const foot = el("div", "foot");
|
|
@@ -10496,7 +10667,7 @@ async function onrampStatus(sinceMs) {
|
|
|
10496
10667
|
function isSuccess(t) {
|
|
10497
10668
|
return /SUCCESS|COMPLETED/i.test(t.status);
|
|
10498
10669
|
}
|
|
10499
|
-
function isPending(t) {
|
|
10670
|
+
function isPending$1(t) {
|
|
10500
10671
|
return /PROGRESS|PENDING|CREATED/i.test(t.status);
|
|
10501
10672
|
}
|
|
10502
10673
|
async function verifyCompletion(openedAt, asset, timeoutMs) {
|
|
@@ -10510,7 +10681,7 @@ async function verifyCompletion(openedAt, asset, timeoutMs) {
|
|
|
10510
10681
|
const done = txs.find((t) => isSuccess(t) && (t.asset === null || t.asset === asset));
|
|
10511
10682
|
if (done)
|
|
10512
10683
|
return done;
|
|
10513
|
-
sawPending = txs.some(isPending);
|
|
10684
|
+
sawPending = txs.some(isPending$1);
|
|
10514
10685
|
}
|
|
10515
10686
|
catch ( /* transient - keep polling until the deadline */_a) { /* transient - keep polling until the deadline */ }
|
|
10516
10687
|
// No completed fill and nothing in flight: one grace poll cycle still
|
|
@@ -10616,6 +10787,570 @@ async function onramp(options = {}) {
|
|
|
10616
10787
|
return Object.assign(Object.assign({}, base), { status: 'closed' });
|
|
10617
10788
|
}
|
|
10618
10789
|
|
|
10790
|
+
// venue-escrow.ts - fund and dispute a venue escrow from inside an OpenApps app (VENUE-GRANT-DESIGN.md).
|
|
10791
|
+
//
|
|
10792
|
+
// An OpenApps app's escrow lives in its venue (`agentescrows/<rootAppId>/e/<escrowId>`), and this
|
|
10793
|
+
// app's session cannot write another app's data. Bounded Auth therefore mints a 5 minute,
|
|
10794
|
+
// single-use GRANT for exactly one create (`POST {humanAuthApiUrl}/app-token/venue-grant`;
|
|
10795
|
+
// contract: packages/cdk/cloudflare/shared/venue-grant.ts, which the SDK cannot import because the
|
|
10796
|
+
// packages publish separately, so only what is needed is mirrored here), and the venue builds the
|
|
10797
|
+
// transaction for the wallet signed in here. The money's only authority is that wallet's
|
|
10798
|
+
// signature, checked onchain by the venue's own rule; the grant adds no power to the app.
|
|
10799
|
+
//
|
|
10800
|
+
// Funding moves money, so it keeps the recovery semantics oapps-fun's retired escrow page proved:
|
|
10801
|
+
// a pending record is saved BEFORE any network call and checked before ever funding again; the
|
|
10802
|
+
// escrow id is stable across retries; an uncertain broadcast is settled by its signature or the
|
|
10803
|
+
// public receipt and never re-sent; one tab funds at a time; and a row that does not match every
|
|
10804
|
+
// term is `taken`, never funded.
|
|
10805
|
+
/** Thrown by every venue escrow call. Branch on `code`; `error.name === 'VenueEscrowError'` also holds across bundled SDK copies. */
|
|
10806
|
+
class VenueEscrowError extends Error {
|
|
10807
|
+
constructor(code, message, details = {}) {
|
|
10808
|
+
super(message);
|
|
10809
|
+
this.name = 'VenueEscrowError';
|
|
10810
|
+
this.code = code;
|
|
10811
|
+
if (details.escrowId !== undefined)
|
|
10812
|
+
this.escrowId = details.escrowId;
|
|
10813
|
+
if (details.cause !== undefined)
|
|
10814
|
+
this.cause = details.cause;
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
const ESCROW_SCHEMA = 'openapps.agent-escrow.v1';
|
|
10818
|
+
const STORAGE_PREFIX = 'bounded.venue-escrow-funding.v1';
|
|
10819
|
+
/** A saved funding that shows no row and no signature this long after its save can no longer land. */
|
|
10820
|
+
const ABANDONED_MS = 5 * 60000;
|
|
10821
|
+
const RECEIPT_POLL_MS = [0, 250, 500, 1000, 1500, 2500, 4000];
|
|
10822
|
+
const ADDRESS = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
10823
|
+
const APP_ID = /^[0-9a-f]{24}$/;
|
|
10824
|
+
const RECORD_ID = /^[0-9a-f]{32}$/;
|
|
10825
|
+
/**
|
|
10826
|
+
* Bounded Auth's own refusals of a grant mint, passed through as codes, with what each means to a
|
|
10827
|
+
* person: the error's message, and so what the reserved signer window shows before it closes.
|
|
10828
|
+
*/
|
|
10829
|
+
const GRANT_REFUSALS = {
|
|
10830
|
+
invalid_request: 'The request was not accepted. Update the app and try again.',
|
|
10831
|
+
invalid_token: 'Your session expired. Sign in again.',
|
|
10832
|
+
origin_not_allowed: "This site isn't allowed to fund escrows for this app.",
|
|
10833
|
+
not_an_openapps_app: "This app can't fund escrows.",
|
|
10834
|
+
wallet_required: 'Sign in with a wallet to fund escrows.',
|
|
10835
|
+
agent_wallet_refused: "An agent's wallet can't fund escrows here.",
|
|
10836
|
+
rate_limited: 'Too many attempts. Try again in a minute.',
|
|
10837
|
+
venue_grant_unavailable: 'Escrows are unavailable right now. Try again shortly.',
|
|
10838
|
+
};
|
|
10839
|
+
const nowSeconds = () => Math.floor(Date.now() / 1000);
|
|
10840
|
+
/** A v0 dispute transaction stops fitting past about 513 reason bytes; this keeps a margin. */
|
|
10841
|
+
const MAX_DISPUTE_REASON_BYTES = 400;
|
|
10842
|
+
const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
10843
|
+
const newRecordId = () => Array.from(getPlatform().getRandomBytes(16), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
10844
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10845
|
+
/** Mirrors `venueGrantPath` in shared/venue-grant.ts: the one path a grant of this kind may create. */
|
|
10846
|
+
function recordPath(kind, rootAppId, id) {
|
|
10847
|
+
return kind === 'escrow' ? `agentescrows/${rootAppId}/e/${id}` : `agentescrowdisputes/${rootAppId}/d/${id}`;
|
|
10848
|
+
}
|
|
10849
|
+
/** Who is signed in, read synchronously: the click handler must not await before it reserves the signer window. */
|
|
10850
|
+
function sessionIdentity() {
|
|
10851
|
+
const appId = core.getConfiguredAppId();
|
|
10852
|
+
let idToken = null;
|
|
10853
|
+
try {
|
|
10854
|
+
idToken = core.getActiveSessionManager().getIdToken();
|
|
10855
|
+
}
|
|
10856
|
+
catch ( /* no session store */_a) { /* no session store */ }
|
|
10857
|
+
if (!appId || !idToken)
|
|
10858
|
+
throw new VenueEscrowError('sign_in_required', 'Sign in before using venue escrows.');
|
|
10859
|
+
const wallet = core.deriveUserIdentityFromIdToken(idToken).address;
|
|
10860
|
+
if (!wallet)
|
|
10861
|
+
throw new VenueEscrowError('wallet_required', 'This session has no wallet to sign with.');
|
|
10862
|
+
return { appId, wallet };
|
|
10863
|
+
}
|
|
10864
|
+
async function mintGrant(kind, id, wallet) {
|
|
10865
|
+
var _a;
|
|
10866
|
+
let idToken = null;
|
|
10867
|
+
try {
|
|
10868
|
+
idToken = await core.getFreshAuthToken(false);
|
|
10869
|
+
}
|
|
10870
|
+
catch ( /* an expired session reads as signed out */_b) { /* an expired session reads as signed out */ }
|
|
10871
|
+
if (!idToken)
|
|
10872
|
+
throw new VenueEscrowError('sign_in_required', 'Your session expired. Sign in again.');
|
|
10873
|
+
if (core.deriveUserIdentityFromIdToken(idToken).address !== wallet) {
|
|
10874
|
+
throw new VenueEscrowError('wallet_changed', 'The signed-in wallet changed. Start again.');
|
|
10875
|
+
}
|
|
10876
|
+
const base = ((await core.getConfig()).humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
10877
|
+
let response;
|
|
10878
|
+
try {
|
|
10879
|
+
response = await fetch(`${base}/app-token/venue-grant`, {
|
|
10880
|
+
method: 'POST',
|
|
10881
|
+
headers: { 'content-type': 'application/json' },
|
|
10882
|
+
body: JSON.stringify({ idToken, kind, id, walletProtocol: 2 }),
|
|
10883
|
+
});
|
|
10884
|
+
}
|
|
10885
|
+
catch (cause) {
|
|
10886
|
+
throw new VenueEscrowError('venue_grant_unavailable', 'Could not reach Bounded Auth to authorize this write.', { cause });
|
|
10887
|
+
}
|
|
10888
|
+
const body = (await response.json().catch(() => null));
|
|
10889
|
+
if (!response.ok) {
|
|
10890
|
+
const error = typeof (body === null || body === void 0 ? void 0 : body.error) === 'string' ? body.error : '';
|
|
10891
|
+
const code = Object.keys(GRANT_REFUSALS).includes(error) ? error : response.status >= 500 ? 'venue_grant_unavailable' : 'venue_grant_refused';
|
|
10892
|
+
throw new VenueEscrowError(code, (_a = GRANT_REFUSALS[code]) !== null && _a !== void 0 ? _a : `Bounded Auth refused the venue grant (${error || response.status}).`);
|
|
10893
|
+
}
|
|
10894
|
+
const fields = body !== null && body !== void 0 ? body : {};
|
|
10895
|
+
const { grant, venueAppId, rootAppId, walletAddress, path } = fields;
|
|
10896
|
+
if (typeof grant !== 'string' || !grant || typeof venueAppId !== 'string' || !APP_ID.test(venueAppId)
|
|
10897
|
+
|| typeof rootAppId !== 'string' || !APP_ID.test(rootAppId) || walletAddress !== wallet || path !== recordPath(kind, rootAppId, id)) {
|
|
10898
|
+
throw new VenueEscrowError('venue_grant_unavailable', 'Bounded Auth answered with a grant that does not match this write.');
|
|
10899
|
+
}
|
|
10900
|
+
return { grant, venueAppId, rootAppId, path };
|
|
10901
|
+
}
|
|
10902
|
+
/* ------------------------------- reading -------------------------------- */
|
|
10903
|
+
/**
|
|
10904
|
+
* Read one venue escrow from the public record: its terms and its state (`pending` until the
|
|
10905
|
+
* venue's hook completes, `failed` when it did not). Resolves null when no such escrow exists.
|
|
10906
|
+
* No sign-in is needed.
|
|
10907
|
+
*/
|
|
10908
|
+
async function getVenueEscrow(input) {
|
|
10909
|
+
var _a;
|
|
10910
|
+
const { rootAppId, escrowId } = input !== null && input !== void 0 ? input : {};
|
|
10911
|
+
if (!APP_ID.test(String(rootAppId)) || !RECORD_ID.test(String(escrowId))) {
|
|
10912
|
+
throw new VenueEscrowError('invalid_input', 'getVenueEscrow needs a rootAppId (24 hex characters) and an escrowId (32 hex characters).');
|
|
10913
|
+
}
|
|
10914
|
+
const base = (_a = (await core.getConfig()).devApiUrl) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '');
|
|
10915
|
+
if (!base)
|
|
10916
|
+
throw new VenueEscrowError('read_unavailable', 'The Bounded developer API is not configured. Pass a Bounded network to init().');
|
|
10917
|
+
let response;
|
|
10918
|
+
try {
|
|
10919
|
+
// The 200 is cacheable for a minute, and a funding is polled through `pending`.
|
|
10920
|
+
response = await fetch(`${base}/public/oapp/${rootAppId}/escrows/${escrowId}`, { cache: 'no-store' });
|
|
10921
|
+
}
|
|
10922
|
+
catch (cause) {
|
|
10923
|
+
throw new VenueEscrowError('read_unavailable', 'Could not reach the Bounded developer API.', { cause });
|
|
10924
|
+
}
|
|
10925
|
+
if (response.status === 404)
|
|
10926
|
+
return null;
|
|
10927
|
+
const body = (await response.json().catch(() => null));
|
|
10928
|
+
if (!response.ok || (body === null || body === void 0 ? void 0 : body.ok) !== true || !body.escrow || typeof body.escrow !== 'object') {
|
|
10929
|
+
throw new VenueEscrowError('read_unavailable', `The escrow could not be read (${response.status}).`);
|
|
10930
|
+
}
|
|
10931
|
+
return body.escrow;
|
|
10932
|
+
}
|
|
10933
|
+
/**
|
|
10934
|
+
* The overrides that point a receipt's reconciliation at the venue it was issued for and the RPC of
|
|
10935
|
+
* the chain it ran on (the same one the write was broadcast through), with no credential.
|
|
10936
|
+
*/
|
|
10937
|
+
async function venueReadOverrides(venueAppId, chain) {
|
|
10938
|
+
var _a;
|
|
10939
|
+
const config = await core.getConfig();
|
|
10940
|
+
const rpcUrl = (_a = core.resolveVenueRpcUrl(config, chain, defaultSolanaRpcUrl)) !== null && _a !== void 0 ? _a : config.rpcUrl;
|
|
10941
|
+
return { _config: Object.assign(Object.assign({}, config), { appId: venueAppId, rpcUrl }), _readOnly: true };
|
|
10942
|
+
}
|
|
10943
|
+
const TERM_KEYS = ['payee', 'amountUsdc', 'challengeSeconds', 'expiresAt', 'ref'];
|
|
10944
|
+
/** Every term the funder signed, which a row must carry in full to be this funding. */
|
|
10945
|
+
const ROW_KEYS = ['schemaVersion', 'funder', 'payee', 'amountUsdc', 'challengeSeconds', 'expiresAt', 'ref', 'at'];
|
|
10946
|
+
/** The first thing wrong with these terms, or null. Shapes only: the venue's rule owns every bound. */
|
|
10947
|
+
function termsProblem(terms, wallet) {
|
|
10948
|
+
if (!terms || typeof terms.payee !== 'string' || !ADDRESS.test(terms.payee))
|
|
10949
|
+
return 'payee must be a Solana address';
|
|
10950
|
+
if (terms.payee === wallet)
|
|
10951
|
+
return 'payee must not be the wallet that is funding';
|
|
10952
|
+
for (const key of ['amountUsdc', 'challengeSeconds', 'expiresAt']) {
|
|
10953
|
+
if (!isCount(terms[key]))
|
|
10954
|
+
return `${key} must be a non-negative integer`;
|
|
10955
|
+
}
|
|
10956
|
+
if (typeof terms.ref !== 'string' || terms.ref.length < 1 || terms.ref.length > 128)
|
|
10957
|
+
return 'ref must be 1 to 128 characters';
|
|
10958
|
+
return null;
|
|
10959
|
+
}
|
|
10960
|
+
function isSavedSubmission(value) {
|
|
10961
|
+
var _a, _b;
|
|
10962
|
+
const saved = value;
|
|
10963
|
+
return !!saved && saved.status === 'submitted' && typeof saved.appId === 'string' && typeof saved.chain === 'string'
|
|
10964
|
+
&& typeof saved.transactionId === 'string' && saved.transactionId.length > 0
|
|
10965
|
+
&& typeof saved.signedTransaction === 'string' && Array.isArray(saved.requestedDocuments)
|
|
10966
|
+
&& 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);
|
|
10967
|
+
}
|
|
10968
|
+
function isPending(value, wallet) {
|
|
10969
|
+
const doc = value === null || value === void 0 ? void 0 : value.document;
|
|
10970
|
+
const sent = (value === null || value === void 0 ? void 0 : value.path) !== undefined;
|
|
10971
|
+
return RECORD_ID.test(String(value === null || value === void 0 ? void 0 : value.escrowId)) && Number.isSafeInteger(value.savedAtMs) && value.savedAtMs > 0
|
|
10972
|
+
&& !!doc && doc.schemaVersion === ESCROW_SCHEMA && doc.funder === wallet && termsProblem(doc, wallet) === null && isCount(doc.at)
|
|
10973
|
+
&& [doc.approvedAt, doc.approvedKind, doc.approvalId, doc.disputedAt, doc.disputedBy, doc.settledAt, doc.settledKind, doc.settledBy].every((field) => field === null)
|
|
10974
|
+
&& (!sent || (APP_ID.test(String(value.rootAppId)) && APP_ID.test(String(value.venueAppId)) && value.path === recordPath('escrow', value.rootAppId, value.escrowId)))
|
|
10975
|
+
&& (value.submission === undefined || isSavedSubmission(value.submission));
|
|
10976
|
+
}
|
|
10977
|
+
function store() {
|
|
10978
|
+
const storage = getPlatform().storage;
|
|
10979
|
+
// A volatile fallback would forget a funding that may have landed, and the next click would send a second one.
|
|
10980
|
+
if (isVolatileStorage(storage))
|
|
10981
|
+
throw new VenueEscrowError('storage_unavailable', 'Venue escrow funding needs durable browser storage.');
|
|
10982
|
+
return storage;
|
|
10983
|
+
}
|
|
10984
|
+
function loadPending(key, wallet) {
|
|
10985
|
+
const storage = store();
|
|
10986
|
+
try {
|
|
10987
|
+
const raw = storage.getItem(key);
|
|
10988
|
+
if (!raw)
|
|
10989
|
+
return null;
|
|
10990
|
+
const value = JSON.parse(raw);
|
|
10991
|
+
if (!isPending(value, wallet))
|
|
10992
|
+
throw new Error('malformed');
|
|
10993
|
+
return value;
|
|
10994
|
+
}
|
|
10995
|
+
catch (_a) {
|
|
10996
|
+
throw new VenueEscrowError('storage_unavailable', 'The saved escrow funding could not be read, so another one will not be started.');
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
function savePending(key, pending) {
|
|
11000
|
+
const storage = store();
|
|
11001
|
+
try {
|
|
11002
|
+
storage.setItem(key, JSON.stringify(pending));
|
|
11003
|
+
}
|
|
11004
|
+
catch (_a) {
|
|
11005
|
+
throw new VenueEscrowError('storage_unavailable', 'The escrow funding could not be saved, so it was not sent.');
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
function clearPending(key, escrowId) {
|
|
11009
|
+
try {
|
|
11010
|
+
const raw = store().getItem(key);
|
|
11011
|
+
if (raw && JSON.parse(raw).escrowId === escrowId)
|
|
11012
|
+
store().removeItem(key);
|
|
11013
|
+
}
|
|
11014
|
+
catch ( /* unreadable: the next call refuses to fund over it */_a) { /* unreadable: the next call refuses to fund over it */ }
|
|
11015
|
+
}
|
|
11016
|
+
function rowVerdict(pending, row) {
|
|
11017
|
+
var _a, _b;
|
|
11018
|
+
if (!ROW_KEYS.every((key) => row[key] === pending.document[key]))
|
|
11019
|
+
return { state: 'taken' };
|
|
11020
|
+
if (row.state === 'failed')
|
|
11021
|
+
return { state: 'failed' };
|
|
11022
|
+
if (row.state === 'pending')
|
|
11023
|
+
return { state: 'pending' };
|
|
11024
|
+
return { state: 'completed', transactionId: (_b = (_a = pending.submission) === null || _a === void 0 ? void 0 : _a.transactionId) !== null && _b !== void 0 ? _b : null };
|
|
11025
|
+
}
|
|
11026
|
+
/** Settle a saved funding without sending anything: the public row first, then its signature, then its age. */
|
|
11027
|
+
async function checkPending(pending) {
|
|
11028
|
+
if (!pending.path)
|
|
11029
|
+
return { state: 'not_applied', reason: 'it was interrupted before the venue was asked.' };
|
|
11030
|
+
let row;
|
|
11031
|
+
try {
|
|
11032
|
+
row = await getVenueEscrow({ rootAppId: pending.rootAppId, escrowId: pending.escrowId });
|
|
11033
|
+
}
|
|
11034
|
+
catch (_a) {
|
|
11035
|
+
return { state: 'pending' };
|
|
11036
|
+
}
|
|
11037
|
+
if (row)
|
|
11038
|
+
return rowVerdict(pending, row);
|
|
11039
|
+
if (pending.submission) {
|
|
11040
|
+
try {
|
|
11041
|
+
const settled = await core.reconcileSetResult(pending.submission, { _overrides: await venueReadOverrides(pending.venueAppId, pending.submission.chain) });
|
|
11042
|
+
if (settled.status === 'confirmed')
|
|
11043
|
+
return { state: 'completed', transactionId: settled.transactionId };
|
|
11044
|
+
if (settled.status === 'expired_not_landed')
|
|
11045
|
+
return { state: 'not_applied', reason: 'it expired before it reached the chain.' };
|
|
11046
|
+
}
|
|
11047
|
+
catch (error) {
|
|
11048
|
+
if (core.writeNotAppliedPhase(error) !== null)
|
|
11049
|
+
return { state: 'not_applied', reason: 'the transaction failed onchain.' };
|
|
11050
|
+
}
|
|
11051
|
+
return { state: 'pending' };
|
|
11052
|
+
}
|
|
11053
|
+
return Date.now() - pending.savedAtMs >= ABANDONED_MS
|
|
11054
|
+
? { state: 'not_applied', reason: 'no transaction was ever recorded for it, and it can no longer land.' }
|
|
11055
|
+
: { state: 'pending' };
|
|
11056
|
+
}
|
|
11057
|
+
/** Poll the public row until it settles: a direct poofnet commit is not funded until the venue's hook stamps it. */
|
|
11058
|
+
async function awaitRow(pending) {
|
|
11059
|
+
for (const delay of RECEIPT_POLL_MS) {
|
|
11060
|
+
if (delay)
|
|
11061
|
+
await pause(delay);
|
|
11062
|
+
let row = null;
|
|
11063
|
+
try {
|
|
11064
|
+
row = await getVenueEscrow({ rootAppId: pending.rootAppId, escrowId: pending.escrowId });
|
|
11065
|
+
}
|
|
11066
|
+
catch ( /* keep polling */_a) { /* keep polling */ }
|
|
11067
|
+
const verdict = row ? rowVerdict(pending, row) : null;
|
|
11068
|
+
if (verdict && verdict.state !== 'pending')
|
|
11069
|
+
return verdict;
|
|
11070
|
+
}
|
|
11071
|
+
return { state: 'pending' };
|
|
11072
|
+
}
|
|
11073
|
+
/** The one place a funding ends: return its result, or release the saved record and throw why not. */
|
|
11074
|
+
function conclude(key, pending, verdict, terms) {
|
|
11075
|
+
const { escrowId } = pending;
|
|
11076
|
+
if (verdict.state === 'pending') {
|
|
11077
|
+
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 });
|
|
11078
|
+
}
|
|
11079
|
+
clearPending(key, escrowId);
|
|
11080
|
+
switch (verdict.state) {
|
|
11081
|
+
case 'completed':
|
|
11082
|
+
if (terms && !TERM_KEYS.every((term) => pending.document[term] === terms[term])) {
|
|
11083
|
+
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 });
|
|
11084
|
+
}
|
|
11085
|
+
return { escrowId, rootAppId: pending.rootAppId, venueAppId: pending.venueAppId, transactionId: verdict.transactionId };
|
|
11086
|
+
case 'taken':
|
|
11087
|
+
throw new VenueEscrowError('taken', 'A different record already holds this escrow id, so this funding did not land.', { escrowId });
|
|
11088
|
+
case 'failed':
|
|
11089
|
+
throw new VenueEscrowError('failed', 'The venue accepted the funding but its hook failed, so nothing moved. Try again.', { escrowId });
|
|
11090
|
+
default:
|
|
11091
|
+
throw new VenueEscrowError('not_applied', `The funding did not go through: ${verdict.reason} nothing moved. Try again.`, { escrowId });
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
/**
|
|
11095
|
+
* Read a thrown write: what the SDK, the wallet or the venue said, as a code. The venue's own code
|
|
11096
|
+
* is read before its status, because a 403 is not always its rule: a grant outside its scope
|
|
11097
|
+
* (`venue_grant_scope`), a forbidden precondition or a signer that does not match the signing
|
|
11098
|
+
* request are not, and only a decline (or the program's own rule rejection) is `rule_refused`.
|
|
11099
|
+
*/
|
|
11100
|
+
function mapWriteError(error) {
|
|
11101
|
+
var _a;
|
|
11102
|
+
if (error instanceof VenueEscrowError)
|
|
11103
|
+
return error;
|
|
11104
|
+
const e = (error !== null && error !== void 0 ? error : {});
|
|
11105
|
+
const text = typeof e.message === 'string' ? e.message : String(error);
|
|
11106
|
+
const code = e.code === 'chain_mismatch' || e.code === 'chain_not_configured' || e.code === 'sponsorship_refused' || e.code === 'sponsorship_unavailable' ? e.code
|
|
11107
|
+
: e.name === 'InsufficientBalanceError' ? 'insufficient_balance'
|
|
11108
|
+
: e.code === 'venue_grant_scope' || e.statusCode === 401 ? 'grant_rejected'
|
|
11109
|
+
: e.isBoundedDecline === true || e.boundedRuleRejection === true ? 'rule_refused'
|
|
11110
|
+
: e.statusCode === 429 ? 'rate_limited'
|
|
11111
|
+
: /^top_level_required/.test(text) ? 'top_level_required'
|
|
11112
|
+
: /popup blocked/i.test(text) ? 'popup_blocked'
|
|
11113
|
+
// The wallet's or the window's own words only: a venue answer that happens to say "denied" is not the user declining.
|
|
11114
|
+
: e.statusCode === undefined && /user rejected|rejected the request|declined|denied|cancel|window was closed/i.test(text) ? 'declined'
|
|
11115
|
+
: 'write_failed';
|
|
11116
|
+
const message = code === 'rule_refused' && typeof ((_a = e.decline) === null || _a === void 0 ? void 0 : _a.message) === 'string' ? e.decline.message
|
|
11117
|
+
: 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.'
|
|
11118
|
+
: text;
|
|
11119
|
+
return new VenueEscrowError(code, message, { cause: error });
|
|
11120
|
+
}
|
|
11121
|
+
/** What a write that returned, rather than threw, means for this funding. */
|
|
11122
|
+
async function settleResult(key, pending, result, terms) {
|
|
11123
|
+
var _a;
|
|
11124
|
+
let current = result;
|
|
11125
|
+
if (current.status === 'submitted') {
|
|
11126
|
+
try {
|
|
11127
|
+
current = await core.waitForSetResult(current, { _overrides: await venueReadOverrides(pending.venueAppId, current.chain), timeoutMs: 60000 });
|
|
11128
|
+
}
|
|
11129
|
+
catch (error) {
|
|
11130
|
+
if (core.writeNotAppliedPhase(error) !== null) {
|
|
11131
|
+
clearPending(key, pending.escrowId);
|
|
11132
|
+
throw mapWriteError(error);
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
}
|
|
11136
|
+
switch (current.status) {
|
|
11137
|
+
case 'confirmed':
|
|
11138
|
+
return conclude(key, pending, { state: 'completed', transactionId: current.transactionId }, terms);
|
|
11139
|
+
case 'committed': {
|
|
11140
|
+
const verdict = await awaitRow(pending);
|
|
11141
|
+
return conclude(key, pending, verdict.state === 'completed' ? { state: 'completed', transactionId: (_a = current.transactionId) !== null && _a !== void 0 ? _a : verdict.transactionId } : verdict, terms);
|
|
11142
|
+
}
|
|
11143
|
+
case 'expired_not_landed':
|
|
11144
|
+
return conclude(key, pending, { state: 'not_applied', reason: 'it expired before it reached the chain.' }, terms);
|
|
11145
|
+
case 'signed':
|
|
11146
|
+
return conclude(key, pending, { state: 'not_applied', reason: 'it was signed but never sent.' }, terms);
|
|
11147
|
+
default:
|
|
11148
|
+
return conclude(key, pending, { state: 'pending' }, terms);
|
|
11149
|
+
}
|
|
11150
|
+
}
|
|
11151
|
+
async function fundOrCheck(key, wallet, terms) {
|
|
11152
|
+
const saved = loadPending(key, wallet);
|
|
11153
|
+
if (saved)
|
|
11154
|
+
return conclude(key, saved, await checkPending(saved), terms);
|
|
11155
|
+
let pending = {
|
|
11156
|
+
escrowId: newRecordId(),
|
|
11157
|
+
savedAtMs: Date.now(),
|
|
11158
|
+
document: {
|
|
11159
|
+
payee: terms.payee, amountUsdc: terms.amountUsdc, challengeSeconds: terms.challengeSeconds, expiresAt: terms.expiresAt, ref: terms.ref,
|
|
11160
|
+
schemaVersion: ESCROW_SCHEMA, funder: wallet, at: nowSeconds(),
|
|
11161
|
+
approvedAt: null, approvedKind: null, approvalId: null, disputedAt: null, disputedBy: null,
|
|
11162
|
+
settledAt: null, settledKind: null, settledBy: null,
|
|
11163
|
+
},
|
|
11164
|
+
};
|
|
11165
|
+
const { escrowId } = pending;
|
|
11166
|
+
savePending(key, pending); // before any network call: the id must survive a lost answer
|
|
11167
|
+
let minted;
|
|
11168
|
+
try {
|
|
11169
|
+
minted = await mintGrant('escrow', escrowId, wallet);
|
|
11170
|
+
// The venue's rule holds `at` within two minutes of the chain clock, and the venue bakes it into the
|
|
11171
|
+
// transaction it builds from this write, so it is taken as late as it can be: after the mint, in the save
|
|
11172
|
+
// that adds the venue path, right before the write. The id and document were already saved before any
|
|
11173
|
+
// network call, and nothing has reached the venue yet, so recovery is unchanged.
|
|
11174
|
+
pending = Object.assign(Object.assign({}, pending), { document: Object.assign(Object.assign({}, pending.document), { at: nowSeconds() }), rootAppId: minted.rootAppId, venueAppId: minted.venueAppId, path: minted.path });
|
|
11175
|
+
savePending(key, pending);
|
|
11176
|
+
}
|
|
11177
|
+
catch (error) {
|
|
11178
|
+
clearPending(key, escrowId); // the venue was never asked, so there is nothing to settle
|
|
11179
|
+
throw error;
|
|
11180
|
+
}
|
|
11181
|
+
let result;
|
|
11182
|
+
try {
|
|
11183
|
+
result = await core.setWithVenueGrant(minted.path, pending.document, {
|
|
11184
|
+
venueAppId: minted.venueAppId,
|
|
11185
|
+
grant: minted.grant,
|
|
11186
|
+
commitGrant: async () => (await mintGrant('escrow', escrowId, wallet)).grant,
|
|
11187
|
+
resolveRpcUrl: defaultSolanaRpcUrl,
|
|
11188
|
+
onBroadcast: (submission) => {
|
|
11189
|
+
const next = Object.assign(Object.assign({}, pending), { submission });
|
|
11190
|
+
savePending(key, next);
|
|
11191
|
+
pending = next;
|
|
11192
|
+
},
|
|
11193
|
+
});
|
|
11194
|
+
}
|
|
11195
|
+
catch (error) {
|
|
11196
|
+
if (core.writeNotAppliedPhase(error) !== null) {
|
|
11197
|
+
clearPending(key, escrowId);
|
|
11198
|
+
throw mapWriteError(error);
|
|
11199
|
+
}
|
|
11200
|
+
// Anything else may follow a commit or broadcast whose answer was lost: settle it by the public
|
|
11201
|
+
// row, then by the saved signature. Never send again.
|
|
11202
|
+
const verdict = await awaitRow(pending);
|
|
11203
|
+
return conclude(key, pending, verdict.state === 'pending' && pending.submission ? await checkPending(pending) : verdict, terms);
|
|
11204
|
+
}
|
|
11205
|
+
return settleResult(key, pending, result, terms);
|
|
11206
|
+
}
|
|
11207
|
+
/** Poll the escrow until it shows disputed: a direct poofnet commit is not a dispute until the venue's hook stamps it. */
|
|
11208
|
+
async function awaitDisputed(rootAppId, escrowId) {
|
|
11209
|
+
for (const delay of RECEIPT_POLL_MS) {
|
|
11210
|
+
if (delay)
|
|
11211
|
+
await pause(delay);
|
|
11212
|
+
const row = await getVenueEscrow({ rootAppId, escrowId }).catch(() => null);
|
|
11213
|
+
if (row && row.disputedAt !== null)
|
|
11214
|
+
return true;
|
|
11215
|
+
}
|
|
11216
|
+
return false;
|
|
11217
|
+
}
|
|
11218
|
+
async function dispute(wallet, escrowId, reason) {
|
|
11219
|
+
const disputeId = newRecordId();
|
|
11220
|
+
const minted = await mintGrant('dispute', disputeId, wallet);
|
|
11221
|
+
let result = null;
|
|
11222
|
+
try {
|
|
11223
|
+
result = await core.setWithVenueGrant(minted.path, { escrowId, reason, at: nowSeconds() }, {
|
|
11224
|
+
venueAppId: minted.venueAppId,
|
|
11225
|
+
grant: minted.grant,
|
|
11226
|
+
commitGrant: async () => (await mintGrant('dispute', disputeId, wallet)).grant,
|
|
11227
|
+
resolveRpcUrl: defaultSolanaRpcUrl,
|
|
11228
|
+
});
|
|
11229
|
+
if (result.status === 'submitted')
|
|
11230
|
+
result = await core.waitForSetResult(result, { _overrides: await venueReadOverrides(minted.venueAppId, result.chain), timeoutMs: 60000 });
|
|
11231
|
+
}
|
|
11232
|
+
catch (error) {
|
|
11233
|
+
if (core.writeNotAppliedPhase(error) !== null)
|
|
11234
|
+
throw mapWriteError(error);
|
|
11235
|
+
// Anything else may follow a commit or broadcast whose answer was lost: the escrow says.
|
|
11236
|
+
}
|
|
11237
|
+
const done = { disputeId, rootAppId: minted.rootAppId, venueAppId: minted.venueAppId };
|
|
11238
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 'confirmed')
|
|
11239
|
+
return Object.assign(Object.assign({}, done), { transactionId: result.transactionId });
|
|
11240
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 'expired_not_landed' || (result === null || result === void 0 ? void 0 : result.status) === 'signed') {
|
|
11241
|
+
throw new VenueEscrowError('not_applied', 'The dispute never reached the chain, so nothing was sent. Try again.', { escrowId });
|
|
11242
|
+
}
|
|
11243
|
+
if (await awaitDisputed(minted.rootAppId, escrowId)) {
|
|
11244
|
+
return Object.assign(Object.assign({}, done), { transactionId: (result === null || result === void 0 ? void 0 : result.status) === 'committed' ? result.transactionId : null });
|
|
11245
|
+
}
|
|
11246
|
+
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 });
|
|
11247
|
+
}
|
|
11248
|
+
/* ----------------------------- one call at a time ------------------------------ */
|
|
11249
|
+
const flights = new Map();
|
|
11250
|
+
/**
|
|
11251
|
+
* Run `run` unless an identical call is already in flight (a double click shares its result) or a
|
|
11252
|
+
* different one is (refused, never silently answered with the other's result). A Turnkey session's
|
|
11253
|
+
* signer window is reserved here, synchronously, because this runs inside the click.
|
|
11254
|
+
*/
|
|
11255
|
+
function startFlow(key, fingerprint, needsSigner, run) {
|
|
11256
|
+
let reservation = null;
|
|
11257
|
+
try {
|
|
11258
|
+
const current = flights.get(key);
|
|
11259
|
+
if (current) {
|
|
11260
|
+
if (current.fingerprint === fingerprint)
|
|
11261
|
+
return current.promise;
|
|
11262
|
+
throw new VenueEscrowError('in_progress', 'A different venue escrow request is already in progress for this wallet.');
|
|
11263
|
+
}
|
|
11264
|
+
if (hasTurnkeyWallet() && needsSigner()) {
|
|
11265
|
+
reservation = reserveSignerPopup();
|
|
11266
|
+
if (!reservation)
|
|
11267
|
+
throw new VenueEscrowError('popup_blocked', 'Call this from a click handler so the wallet approval window can open, and allow popups for this site.');
|
|
11268
|
+
}
|
|
11269
|
+
// A flow that fails before the signer loads (a grant the venue refuses, a rule) leaves the reserved
|
|
11270
|
+
// window to explain itself with the error's words for a moment; one that signed has closed it already.
|
|
11271
|
+
const settled = (error) => {
|
|
11272
|
+
var _a;
|
|
11273
|
+
if (((_a = flights.get(key)) === null || _a === void 0 ? void 0 : _a.promise) === promise)
|
|
11274
|
+
flights.delete(key);
|
|
11275
|
+
reservation === null || reservation === void 0 ? void 0 : reservation.release(error instanceof Error ? error.message : undefined);
|
|
11276
|
+
};
|
|
11277
|
+
const promise = run().then((value) => { settled(); return value; }, (error) => { settled(error); throw error; });
|
|
11278
|
+
flights.set(key, { fingerprint, promise });
|
|
11279
|
+
return promise;
|
|
11280
|
+
}
|
|
11281
|
+
catch (error) {
|
|
11282
|
+
reservation === null || reservation === void 0 ? void 0 : reservation.release(error instanceof Error ? error.message : undefined);
|
|
11283
|
+
return Promise.reject(error);
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
/** One tab funds at a time: the saved record is shared by every tab of this browser. */
|
|
11287
|
+
function withLock(key, run) {
|
|
11288
|
+
var _a;
|
|
11289
|
+
const locks = (_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.locks;
|
|
11290
|
+
if (!(locks === null || locks === void 0 ? void 0 : locks.request))
|
|
11291
|
+
return Promise.reject(new VenueEscrowError('lock_unavailable', 'Venue escrow funding needs the Web Locks API, so that one tab funds at a time.'));
|
|
11292
|
+
return locks.request(key, { ifAvailable: true }, async (lock) => {
|
|
11293
|
+
if (!lock)
|
|
11294
|
+
throw new VenueEscrowError('in_progress', 'This escrow is already being funded in another tab.');
|
|
11295
|
+
return run();
|
|
11296
|
+
});
|
|
11297
|
+
}
|
|
11298
|
+
/* --------------------------------- public API ---------------------------------- */
|
|
11299
|
+
/**
|
|
11300
|
+
* Lock USDC in the venue escrow for `terms.payee`, signed by the wallet signed in here.
|
|
11301
|
+
* MUST be called from a user click: an email (Turnkey) wallet's approval opens in its own Bounded
|
|
11302
|
+
* window, which a browser only allows inside the click (it is opened here, before anything is
|
|
11303
|
+
* awaited), and the approval shows the decoded terms. A Phantom or Wallet Standard wallet keeps
|
|
11304
|
+
* its own prompt. On a real-chain venue the transaction is broadcast on the VENUE's chain: through
|
|
11305
|
+
* the app's own `rpcUrl` when the app configured that same `chain` too (an `rpcUrl` alone does not
|
|
11306
|
+
* say which chain it speaks), otherwise through the platform's default RPC for the venue's chain, so
|
|
11307
|
+
* an app that never configured a chain just works. An app that configured a different `chain` is
|
|
11308
|
+
* refused (`chain_mismatch`) before anything is signed.
|
|
11309
|
+
*
|
|
11310
|
+
* Funding is made safe to retry: a funding that was interrupted (page closed, answer lost) is
|
|
11311
|
+
* saved before anything is sent, and the next call checks it instead of funding again, so pass
|
|
11312
|
+
* the same terms and it resolves to the same `escrowId`. Store `escrowId` on your own record.
|
|
11313
|
+
* See {@link VenueEscrowErrorCode} for the failures, and `pending` in particular.
|
|
11314
|
+
*/
|
|
11315
|
+
function fundVenueEscrow(terms) {
|
|
11316
|
+
try {
|
|
11317
|
+
const { appId, wallet } = sessionIdentity();
|
|
11318
|
+
const problem = termsProblem(terms, wallet);
|
|
11319
|
+
if (problem)
|
|
11320
|
+
throw new VenueEscrowError('invalid_input', `fundVenueEscrow: ${problem}.`);
|
|
11321
|
+
const checked = {
|
|
11322
|
+
payee: terms.payee, amountUsdc: terms.amountUsdc, challengeSeconds: terms.challengeSeconds, expiresAt: terms.expiresAt, ref: terms.ref,
|
|
11323
|
+
};
|
|
11324
|
+
const key = `${STORAGE_PREFIX}:${appId}:${wallet}`;
|
|
11325
|
+
return startFlow(key, JSON.stringify(checked), () => !loadPending(key, wallet), () => withLock(key, () => fundOrCheck(key, wallet, checked)));
|
|
11326
|
+
}
|
|
11327
|
+
catch (error) {
|
|
11328
|
+
return Promise.reject(error);
|
|
11329
|
+
}
|
|
11330
|
+
}
|
|
11331
|
+
/**
|
|
11332
|
+
* Dispute a funded venue escrow as its funder (or payee), signed by the wallet signed in here.
|
|
11333
|
+
* MUST be called from a user click, for the same reason as {@link fundVenueEscrow}. `reason` is
|
|
11334
|
+
* 1 to 400 bytes of UTF-8: the venue allows 1000 characters, but a longer reason does not fit one
|
|
11335
|
+
* legacy-format transaction. Nothing is saved: a dispute that already landed makes the venue refuse
|
|
11336
|
+
* a second one (`rule_refused`).
|
|
11337
|
+
*/
|
|
11338
|
+
function disputeVenueEscrow(input) {
|
|
11339
|
+
try {
|
|
11340
|
+
const { appId, wallet } = sessionIdentity();
|
|
11341
|
+
const { escrowId, reason } = input !== null && input !== void 0 ? input : {};
|
|
11342
|
+
if (!RECORD_ID.test(String(escrowId)))
|
|
11343
|
+
throw new VenueEscrowError('invalid_input', 'disputeVenueEscrow: escrowId must be 32 hex characters.');
|
|
11344
|
+
if (typeof reason !== 'string' || reason.length < 1 || new TextEncoder().encode(reason).length > MAX_DISPUTE_REASON_BYTES) {
|
|
11345
|
+
throw new VenueEscrowError('invalid_input', `disputeVenueEscrow: reason must be 1 to ${MAX_DISPUTE_REASON_BYTES} bytes.`);
|
|
11346
|
+
}
|
|
11347
|
+
return startFlow(`dispute:${appId}:${wallet}:${escrowId}`, reason, () => true, () => dispute(wallet, escrowId, reason));
|
|
11348
|
+
}
|
|
11349
|
+
catch (error) {
|
|
11350
|
+
return Promise.reject(error);
|
|
11351
|
+
}
|
|
11352
|
+
}
|
|
11353
|
+
|
|
10619
11354
|
/**
|
|
10620
11355
|
* DS3-0414: does the transaction already carry a REAL signer signature? A Solana
|
|
10621
11356
|
* signature is computed over the compiled message, which INCLUDES recentBlockhash, so
|
|
@@ -11017,25 +11752,6 @@ class PrivyExpoProvider {
|
|
|
11017
11752
|
}
|
|
11018
11753
|
}
|
|
11019
11754
|
|
|
11020
|
-
/**
|
|
11021
|
-
* Default provider discovery: Phantom-first, then any Wallet-Standard
|
|
11022
|
-
* `window.solana`. Kept tiny + synchronous (no heavy imports) so it works inside
|
|
11023
|
-
* a user gesture and can be eagerly exported from the SDK entry.
|
|
11024
|
-
*/
|
|
11025
|
-
function defaultInjectedProvider() {
|
|
11026
|
-
var _a;
|
|
11027
|
-
if (typeof window === 'undefined')
|
|
11028
|
-
return null;
|
|
11029
|
-
const w = window;
|
|
11030
|
-
const phantom = (_a = w === null || w === void 0 ? void 0 : w.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
11031
|
-
if (phantom && typeof phantom.signMessage === 'function')
|
|
11032
|
-
return phantom;
|
|
11033
|
-
const generic = w === null || w === void 0 ? void 0 : w.solana;
|
|
11034
|
-
if (generic && typeof generic.signMessage === 'function')
|
|
11035
|
-
return generic;
|
|
11036
|
-
return null;
|
|
11037
|
-
}
|
|
11038
|
-
|
|
11039
11755
|
// injected-evm-wallet-types.ts — tiny, dependency-free surface for the EVM
|
|
11040
11756
|
// WALLET LOGIN path (BYO wallet via SIWE). The EVM twin of
|
|
11041
11757
|
// injected-wallet-types.ts: kept separate from injected-evm-wallet-provider.ts
|
|
@@ -11435,7 +12151,9 @@ exports.OffchainAuthProvider = OffchainAuthProvider;
|
|
|
11435
12151
|
exports.PARK_REASONS = PARK_REASONS;
|
|
11436
12152
|
exports.PrivyExpoProvider = PrivyExpoProvider;
|
|
11437
12153
|
exports.RUN_STATES = RUN_STATES;
|
|
12154
|
+
exports.SELECTED_WALLET_UNAVAILABLE_ERR = SELECTED_WALLET_UNAVAILABLE_ERR;
|
|
11438
12155
|
exports.TERMINAL_STATES = TERMINAL_STATES;
|
|
12156
|
+
exports.VenueEscrowError = VenueEscrowError;
|
|
11439
12157
|
exports.WalletConfigError = WalletConfigError;
|
|
11440
12158
|
exports.apps = apps;
|
|
11441
12159
|
exports.bs58 = bs58;
|
|
@@ -11447,10 +12165,12 @@ exports.connectBroadcast = connectBroadcast;
|
|
|
11447
12165
|
exports.connectServiceSession = connectServiceSession;
|
|
11448
12166
|
exports.defaultInjectedEvmProvider = defaultInjectedEvmProvider;
|
|
11449
12167
|
exports.defaultInjectedProvider = defaultInjectedProvider;
|
|
12168
|
+
exports.disputeVenueEscrow = disputeVenueEscrow;
|
|
11450
12169
|
exports.ensureSharedWalletLoginReady = ensureSharedWalletLoginReady;
|
|
11451
12170
|
exports.ensureSignedIn = ensureSignedIn;
|
|
11452
12171
|
exports.ensureWalletLoginReady = ensureWalletLoginReady;
|
|
11453
12172
|
exports.firstStandardWalletProvider = firstStandardWalletProvider;
|
|
12173
|
+
exports.fundVenueEscrow = fundVenueEscrow;
|
|
11454
12174
|
exports.getAuthLoading = getAuthLoading;
|
|
11455
12175
|
exports.getAuthProvider = getAuthProvider;
|
|
11456
12176
|
exports.getCurrentUser = getCurrentUser;
|
|
@@ -11460,6 +12180,7 @@ exports.getOrCreateTurnkeyWallet = getOrCreateTurnkeyWallet;
|
|
|
11460
12180
|
exports.getPlatform = getPlatform;
|
|
11461
12181
|
exports.getTurnkeyWalletRecoveryUrl = getTurnkeyWalletRecoveryUrl;
|
|
11462
12182
|
exports.getTurnkeyWalletSessionState = getTurnkeyWalletSessionState;
|
|
12183
|
+
exports.getVenueEscrow = getVenueEscrow;
|
|
11463
12184
|
exports.hasTurnkeyWallet = hasTurnkeyWallet;
|
|
11464
12185
|
exports.init = init;
|
|
11465
12186
|
exports.login = login;
|
|
@@ -11491,9 +12212,10 @@ exports.signSolanaMessageViaTurnkey = signSolanaMessageViaTurnkey;
|
|
|
11491
12212
|
exports.signTransactionV1WithTurnkey = signTransactionV1WithTurnkey;
|
|
11492
12213
|
exports.signTransactionWithTurnkey = signTransactionWithTurnkey;
|
|
11493
12214
|
exports.startTurnkeyEmailLogin = startTurnkeyEmailLogin;
|
|
12215
|
+
exports.supportsWalletSignIn = supportsWalletSignIn;
|
|
11494
12216
|
exports.transactionHasSignature = transactionHasSignature;
|
|
11495
12217
|
exports.useAuth = useAuth;
|
|
11496
12218
|
exports.useBuildRun = useBuildRun;
|
|
11497
12219
|
exports.useQuery = useQuery;
|
|
11498
12220
|
exports.waitForAuthReady = waitForAuthReady;
|
|
11499
|
-
//# sourceMappingURL=index-
|
|
12221
|
+
//# sourceMappingURL=index-nU0ZSfWs.js.map
|