@bounded-sh/core 0.0.113 → 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.
Files changed (32) hide show
  1. package/dist/client/operations.d.ts +14 -0
  2. package/dist/client/reconcile.d.ts +18 -0
  3. package/dist/client/venue-grant-write.d.ts +62 -0
  4. package/dist/client/write-outcome.d.ts +1 -0
  5. package/dist/{index-WIHuQp4L.js → index-5TMFraRK.js} +230 -61
  6. package/dist/index-5TMFraRK.js.map +1 -0
  7. package/dist/{index-CkgEqMkJ.mjs → index-BilGFb3V.mjs} +229 -62
  8. package/dist/index-BilGFb3V.mjs.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +3 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/{realtime-store-BIB8v265.mjs → realtime-store-Bc71mTWr.mjs} +2 -2
  14. package/dist/{realtime-store-BIB8v265.mjs.map → realtime-store-Bc71mTWr.mjs.map} +1 -1
  15. package/dist/{realtime-store-7itLaAao.js → realtime-store-BiChlioj.js} +2 -2
  16. package/dist/{realtime-store-7itLaAao.js.map → realtime-store-BiChlioj.js.map} +1 -1
  17. package/dist/{solana-message-BBMxFFjM.mjs → solana-message-BNGEBst_.mjs} +2 -2
  18. package/dist/{solana-message-BBMxFFjM.mjs.map → solana-message-BNGEBst_.mjs.map} +1 -1
  19. package/dist/{solana-message-Ce6_jR6H.js → solana-message-BVwD6WVm.js} +2 -2
  20. package/dist/{solana-message-Ce6_jR6H.js.map → solana-message-BVwD6WVm.js.map} +1 -1
  21. package/dist/{solana-operations-CNeO_lJ2.js → solana-operations-B3u6Arbf.js} +3 -3
  22. package/dist/{solana-operations-CNeO_lJ2.js.map → solana-operations-B3u6Arbf.js.map} +1 -1
  23. package/dist/{solana-operations-Cshh2EfH.mjs → solana-operations-BLbRO1rE.mjs} +3 -3
  24. package/dist/{solana-operations-Cshh2EfH.mjs.map → solana-operations-BLbRO1rE.mjs.map} +1 -1
  25. package/dist/{solana-v1-operations-c0p0YESV.js → solana-v1-operations-BxTmxUMN.js} +3 -3
  26. package/dist/{solana-v1-operations-c0p0YESV.js.map → solana-v1-operations-BxTmxUMN.js.map} +1 -1
  27. package/dist/{solana-v1-operations-B8vV5teG.mjs → solana-v1-operations-b1rmLBUP.mjs} +3 -3
  28. package/dist/{solana-v1-operations-B8vV5teG.mjs.map → solana-v1-operations-b1rmLBUP.mjs.map} +1 -1
  29. package/dist/utils/refresh-error.d.ts +4 -0
  30. package/package.json +2 -2
  31. package/dist/index-CkgEqMkJ.mjs.map +0 -1
  32. package/dist/index-WIHuQp4L.js.map +0 -1
@@ -30,6 +30,20 @@ export type RequestOverrides = {
30
30
  * deterministically; production builds a `Connection` from `config.rpcUrl`.
31
31
  */
32
32
  _solanaRpc?: SolanaWriteRpc;
33
+ /**
34
+ * @internal The venue grant write lane (venue-grant-write.ts). A single-use grant, not the
35
+ * caller's session, authenticates the write, so `setMany` also lets the lane refuse the
36
+ * execution lane a 202 reports, takes the poofnet commit's credential from the lane, and
37
+ * skips the post-confirm mirror read a grant cannot authorize. App code never sets it.
38
+ */
39
+ _grantLane?: GrantLane;
40
+ };
41
+ /** @internal See {@link RequestOverrides._grantLane}. */
42
+ export type GrantLane = {
43
+ /** Throws to refuse `chain`, the execution lane the 202 reported, before anything is signed. */
44
+ assertLane(chain: string): void;
45
+ /** The credential for the poofnet `PUT items/signed` commit. A grant is single use, so each call mints a fresh one. */
46
+ commitHeaders(): Promise<Record<string, string>>;
33
47
  };
34
48
  export type SetOptions = {
35
49
  shouldSubmitTx?: boolean;
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Reconciliation for a `submitted` write receipt.
3
+ *
4
+ * A returned object cannot transition itself, and calling `set` again builds a
5
+ * FRESH transaction — which is exactly what must not happen while the first
6
+ * signature may still land. Without this call the `submitted` state is a dead
7
+ * end and its terminal exits are unreachable.
8
+ *
9
+ * Everything here polls the SAME signature. Nothing here ever builds a new
10
+ * transaction or broadcasts anything.
11
+ */
12
+ import type { ClientConfig } from './config';
1
13
  import type { RequestOverrides } from './operations';
2
14
  import type { SetResult } from '../types';
3
15
  export type ReconcileSetResultOptions = {
@@ -18,6 +30,12 @@ export type WaitForSetResultOptions = ReconcileSetResultOptions & {
18
30
  /** Cancels the wait. The latest `submitted` receipt is returned, never discarded. */
19
31
  signal?: AbortSignal;
20
32
  };
33
+ /**
34
+ * The Solana execution lane a client config REQUIRES, or `null` when the config
35
+ * carries no constraint. Only a positive contradiction rejects a receipt — an
36
+ * unmapped or unset `chain` is not evidence of a mismatch.
37
+ */
38
+ export declare function requiredSolanaChain(config: ClientConfig): string | null;
21
39
  /**
22
40
  * Poll the SAME signature a `submitted` receipt carries and return the
23
41
  * resulting `SetResult`.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The venue grant write lane: create ONE document in another app (an OpenApps venue) as the
3
+ * wallet signed in here, authorized by a single-use grant instead of a venue session
4
+ * (VENUE-GRANT-DESIGN.md). The grant only lets the venue build that one transaction for that
5
+ * wallet; the wallet's own signature, checked by the venue's own rule, is the authority.
6
+ *
7
+ * The grant itself is minted by Bounded Auth at `POST {humanAuthApiUrl}/app-token/venue-grant`
8
+ * (contract: packages/cdk/cloudflare/shared/venue-grant.ts, which this package cannot import
9
+ * because the SDK packages publish separately). This module only spends one.
10
+ *
11
+ * It reuses `setMany`'s request and 202 handling through `_overrides`: the `_config` snapshot is
12
+ * this app's own config re-pointed at the VENUE app id, so the pre-sign validator (OA-0001) binds
13
+ * the venue app id and the full expected intent, and the signer stays this app's own AuthProvider
14
+ * (a Turnkey session or a Wallet Standard wallet). Nothing here is reachable from `createClient()`,
15
+ * which stays read-only.
16
+ *
17
+ * The chain a write runs on is the VENUE's, stated by its 202, never the app's: the app's own
18
+ * `chain` can only agree with it or be refused, and the transaction is broadcast through the app's
19
+ * `rpcUrl` only when the app configured that same chain, otherwise through `resolveRpcUrl(chain)`.
20
+ */
21
+ import { type ClientConfig } from './config';
22
+ import { type SetOptions } from './operations';
23
+ import type { SetResult, SolanaWriteRpc } from '../types';
24
+ export interface VenueGrantWriteOptions {
25
+ /** The venue app the grant was minted for. The write and its 202 validation bind THIS app id, never the caller's own. */
26
+ venueAppId: string;
27
+ /** The single-use grant that authorizes this one create. */
28
+ grant: string;
29
+ /**
30
+ * Mints a fresh grant for the same kind and id. The `PUT items` that built the approval spent the
31
+ * first, so the poofnet `PUT items/signed` commit needs its own. Called only on poofnet, after the
32
+ * wallet has signed.
33
+ */
34
+ commitGrant: () => Promise<string>;
35
+ /** As `set`'s `onBroadcast`: persist the signed transaction's receipt before it is broadcast (Solana lanes). */
36
+ onBroadcast?: SetOptions['onBroadcast'];
37
+ /**
38
+ * The RPC for a Solana lane the app did not configure itself: given the chain the venue's 202
39
+ * reported (`solana_devnet` or `solana_mainnet`), its RPC url, or null for none. The client SDK
40
+ * passes its platform default. See {@link resolveVenueRpcUrl} for how it ranks against the app's own.
41
+ */
42
+ resolveRpcUrl?: (chain: string) => string | null | undefined;
43
+ /** @internal Test seam, as `RequestOverrides._solanaRpc`. */
44
+ _solanaRpc?: SolanaWriteRpc;
45
+ }
46
+ /**
47
+ * The RPC a venue write on Solana lane `chain` is broadcast through, and a saved receipt of it is
48
+ * reconciled through. The app's own `rpcUrl` counts only when the app also configured this chain
49
+ * (an `rpcUrl` alone does not say which chain it speaks); otherwise `resolveRpcUrl(chain)`. Null
50
+ * when neither yields one.
51
+ *
52
+ * @internal Shared by the lane and by the client SDK's reconciliation of a saved receipt.
53
+ */
54
+ export declare function resolveVenueRpcUrl(config: ClientConfig, chain: string, resolveRpcUrl?: (chain: string) => string | null | undefined): string | null;
55
+ /**
56
+ * Write `document` at `path` in the venue `venueAppId` with a single-use grant. Resolves to the
57
+ * same discriminated {@link SetResult} as `set`, and throws exactly as `set` does, so the same
58
+ * `isWriteNotApplied` reading applies.
59
+ *
60
+ * @internal Used by `fundVenueEscrow` and `disputeVenueEscrow` in @bounded-sh/client. Not a stable API.
61
+ */
62
+ export declare function setWithVenueGrant(path: string, document: Record<string, unknown>, options: VenueGrantWriteOptions): Promise<SetResult>;
@@ -14,6 +14,7 @@
14
14
  * `onBroadcast` hook refusing). Nothing was sent.
15
15
  * - `broadcast_rejected`: the RPC definitively refused the signed bytes
16
16
  * (preflight simulation, invalid transaction). They never entered the network.
17
+ * On poofnet, the realtime worker answering the signed commit with a 4xx.
17
18
  * - `failed_onchain`: the transaction landed and failed, so the write reverted.
18
19
  *
19
20
  * The tag is plain data on the thrown object (`writeOutcome` + `writePhase`),
@@ -421,6 +421,97 @@ axiosRetry.exponentialDelay = exponentialDelay;
421
421
  axiosRetry.linearDelay = linearDelay;
422
422
  axiosRetry.isRetryableError = isRetryableError;
423
423
 
424
+ // Classify a session-refresh failure as a DEFINITIVE rejection of the credential
425
+ // vs a TRANSPORT failure (DS4-0054).
426
+ //
427
+ // The restore path refreshes an expired token at boot. The old code destroyed the
428
+ // local session on ANY refresh failure, so a network blip - common on mobile -
429
+ // signed the user out. Only a real HTTP rejection of the refresh token proves the
430
+ // session is invalid; anything else is unknown, and the safe move is to RETAIN local
431
+ // state and retry. Keeping a genuinely dead session locally is harmless: every
432
+ // request still fails closed server-side, and the next refresh that reaches the
433
+ // server clears it. Destroying a live session on a blip is user-visible damage.
434
+ //
435
+ // `/session/refresh` answers 401 (session_revoked / invalid_grant) when the token is
436
+ // dead. 400 (bad request), 408/429 (transient), and 5xx are NOT proof the session is
437
+ // invalid, and a network/DNS/timeout error carries no HTTP response at all.
438
+ //
439
+ // This is the single classifier for every refresh path: the per-request freshness
440
+ // check (auth-token-freshness) and the boot-time restore paths (web + RN session
441
+ // managers) all import it, so "definitive" means the same thing everywhere. It lives
442
+ // in this leaf module to avoid an import cycle between those consumers.
443
+ function isDefinitiveRejection(err) {
444
+ var _a;
445
+ const status = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status;
446
+ return status === 401 || status === 403;
447
+ }
448
+ // How long past the id token's own `exp` a session may survive purely on
449
+ // transport failures.
450
+ //
451
+ // "Retain on a blip" must not become "retain forever". A statusless failure is
452
+ // unknown, so the first ones are absorbed - but a revoked session, or a gateway
453
+ // that rewrites the issuer's 401 into a 502, would otherwise keep
454
+ // isAuthenticated() reporting a signed-in user for as long as the device never
455
+ // reaches the issuer again. One day covers an offline flight or a bad-network day
456
+ // while making the retention finite: past it, an unrefreshable session is cleared
457
+ // and the user is asked to sign in again.
458
+ const TRANSPORT_RETENTION_GRACE_MS = 24 * 60 * 60 * 1000;
459
+ /**
460
+ * May a session whose refresh failed WITHOUT a definitive rejection be kept?
461
+ *
462
+ * `expSeconds` is the id token's `exp` claim. Fails closed: a missing or
463
+ * non-numeric expiry is not a usable bound, so such a session is never retained.
464
+ */
465
+ function mayRetainAfterTransportFailure(expSeconds, now = Date.now()) {
466
+ if (typeof expSeconds !== 'number' || !Number.isFinite(expSeconds))
467
+ return false;
468
+ return now <= expSeconds * 1000 + TRANSPORT_RETENTION_GRACE_MS;
469
+ }
470
+ // Backoff between refresh attempts that failed WITHOUT a definitive rejection.
471
+ //
472
+ // A transport failure retains the session (above), and every authenticated request
473
+ // and the realtime token timer asks for a fresh token - so without a pause, an issuer
474
+ // that answers 503 or 429 gets one refresh per request from every open tab, which is
475
+ // the exact load that keeps it down. After such a failure the same refresh token is
476
+ // not sent again until the backoff passes: 15s, doubling, capped at 10 minutes, or the
477
+ // issuer's own Retry-After when it is longer. Callers inside the window get a
478
+ // non-definitive error at once (`code: 'refresh_backoff'`) and treat it like the
479
+ // failure that started it. A success, or a different token, starts clean.
480
+ const REFRESH_BACKOFF_BASE_MS = 15000;
481
+ const REFRESH_BACKOFF_MAX_MS = 10 * 60000;
482
+ const refreshBackoff = new Map();
483
+ function retryAfterMs(err) {
484
+ var _a, _b;
485
+ const headers = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.headers;
486
+ const raw = headers && ((_b = headers['retry-after']) !== null && _b !== void 0 ? _b : headers['Retry-After']);
487
+ const seconds = Number(raw);
488
+ return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds * 1000, REFRESH_BACKOFF_MAX_MS) : 0;
489
+ }
490
+ /** Throws a non-definitive `refresh_backoff` error while `key` is still backing off. */
491
+ function assertRefreshAllowed(key, now = Date.now()) {
492
+ const entry = refreshBackoff.get(key);
493
+ if (!entry || now >= entry.until)
494
+ return;
495
+ const err = new Error('Session refresh is backing off after a failed attempt');
496
+ err.code = 'refresh_backoff';
497
+ err.retryAt = entry.until;
498
+ throw err;
499
+ }
500
+ /** Record the outcome of a refresh attempt for `key`. */
501
+ function recordRefreshOutcome(key, err, now = Date.now()) {
502
+ var _a, _b;
503
+ if (!err || isDefinitiveRejection(err)) {
504
+ refreshBackoff.delete(key);
505
+ return;
506
+ }
507
+ const failures = ((_b = (_a = refreshBackoff.get(key)) === null || _a === void 0 ? void 0 : _a.failures) !== null && _b !== void 0 ? _b : 0) + 1;
508
+ const backoff = Math.min(REFRESH_BACKOFF_BASE_MS * 2 ** (failures - 1), REFRESH_BACKOFF_MAX_MS);
509
+ refreshBackoff.set(key, { failures, until: now + Math.max(backoff, retryAfterMs(err)) });
510
+ // One entry per refresh token; a long-lived tab rotates through a handful at most.
511
+ if (refreshBackoff.size > 32)
512
+ refreshBackoff.delete(refreshBackoff.keys().next().value);
513
+ }
514
+
424
515
  /**
425
516
  * Resolve the refresh/revoke base URL to a CONFIGURED issuer only. The session's
426
517
  * `issuer` is persisted in client storage; if that storage is tampered (e.g. XSS)
@@ -574,6 +665,9 @@ async function refreshSession(refreshToken, issuer) {
574
665
  const existing = refreshInFlightByToken.get(dedupeKey);
575
666
  if (existing)
576
667
  return existing;
668
+ // Inside a backoff window this throws at once, so a failing issuer is not asked again
669
+ // by every request and timer in the page (see refresh-error.ts).
670
+ assertRefreshAllowed(dedupeKey);
577
671
  let currentFlight;
578
672
  currentFlight = (async () => {
579
673
  try {
@@ -587,8 +681,13 @@ async function refreshSession(refreshToken, issuer) {
587
681
  refreshToken,
588
682
  appId
589
683
  }, { headers: { 'Content-Type': 'application/json' }, timeout: 30000 });
684
+ recordRefreshOutcome(dedupeKey, null);
590
685
  return response.data;
591
686
  }
687
+ catch (error) {
688
+ recordRefreshOutcome(dedupeKey, error);
689
+ throw error;
690
+ }
592
691
  finally {
593
692
  // Only remove our own entry, and only if it is still the active flight
594
693
  // for this token (a later refresh of the same token cannot have replaced
@@ -646,53 +745,6 @@ async function revokeSession(refreshToken, issuer) {
646
745
  async function signSessionCreateMessage(_signMessageFunction) {
647
746
  }
648
747
 
649
- // Classify a session-refresh failure as a DEFINITIVE rejection of the credential
650
- // vs a TRANSPORT failure (DS4-0054).
651
- //
652
- // The restore path refreshes an expired token at boot. The old code destroyed the
653
- // local session on ANY refresh failure, so a network blip - common on mobile -
654
- // signed the user out. Only a real HTTP rejection of the refresh token proves the
655
- // session is invalid; anything else is unknown, and the safe move is to RETAIN local
656
- // state and retry. Keeping a genuinely dead session locally is harmless: every
657
- // request still fails closed server-side, and the next refresh that reaches the
658
- // server clears it. Destroying a live session on a blip is user-visible damage.
659
- //
660
- // `/session/refresh` answers 401 (session_revoked / invalid_grant) when the token is
661
- // dead. 400 (bad request), 408/429 (transient), and 5xx are NOT proof the session is
662
- // invalid, and a network/DNS/timeout error carries no HTTP response at all.
663
- //
664
- // This is the single classifier for every refresh path: the per-request freshness
665
- // check (auth-token-freshness) and the boot-time restore paths (web + RN session
666
- // managers) all import it, so "definitive" means the same thing everywhere. It lives
667
- // in this leaf module to avoid an import cycle between those consumers.
668
- function isDefinitiveRejection(err) {
669
- var _a;
670
- const status = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status;
671
- return status === 401 || status === 403;
672
- }
673
- // How long past the id token's own `exp` a session may survive purely on
674
- // transport failures.
675
- //
676
- // "Retain on a blip" must not become "retain forever". A statusless failure is
677
- // unknown, so the first ones are absorbed - but a revoked session, or a gateway
678
- // that rewrites the issuer's 401 into a 502, would otherwise keep
679
- // isAuthenticated() reporting a signed-in user for as long as the device never
680
- // reaches the issuer again. One day covers an offline flight or a bad-network day
681
- // while making the retention finite: past it, an unrefreshable session is cleared
682
- // and the user is asked to sign in again.
683
- const TRANSPORT_RETENTION_GRACE_MS = 24 * 60 * 60 * 1000;
684
- /**
685
- * May a session whose refresh failed WITHOUT a definitive rejection be kept?
686
- *
687
- * `expSeconds` is the id token's `exp` claim. Fails closed: a missing or
688
- * non-numeric expiry is not a usable bound, so such a session is never retained.
689
- */
690
- function mayRetainAfterTransportFailure(expSeconds, now = Date.now()) {
691
- if (typeof expSeconds !== 'number' || !Number.isFinite(expSeconds))
692
- return false;
693
- return now <= expSeconds * 1000 + TRANSPORT_RETENTION_GRACE_MS;
694
- }
695
-
696
748
  // Shared session-binding guard for the web + React Native session managers, so
697
749
  // the cross-principal refresh guard (#220 / #221) lives in ONE place and the two
698
750
  // managers can't drift apart. Base64url decoding differs by platform (browser
@@ -4190,7 +4242,7 @@ class ServerSessionManager {
4190
4242
  const sessionConfig = (_a = this.sessionConfig) !== null && _a !== void 0 ? _a : undefined;
4191
4243
  /* fetch nonce from auth API (bound to the snapshotted app+issuer) */
4192
4244
  const nonce = await genAuthNonce(sessionConfig);
4193
- const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-Ce6_jR6H.js'); });
4245
+ const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-BVwD6WVm.js'); });
4194
4246
  const message = await genSolanaMessage(address, nonce, sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId);
4195
4247
  /* sign the message */
4196
4248
  const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
@@ -4539,6 +4591,16 @@ function classifyToken(token, nowMs = Date.now(), skewMs = TOKEN_EXPIRY_SKEW_MS)
4539
4591
  return 'expiring-soon';
4540
4592
  return 'fresh';
4541
4593
  }
4594
+ function tokenExpSeconds(token) {
4595
+ var _a;
4596
+ try {
4597
+ const exp = (_a = JSON.parse(decodeBase64Url(token.split('.')[1]))) === null || _a === void 0 ? void 0 : _a.exp;
4598
+ return typeof exp === 'number' ? exp : undefined;
4599
+ }
4600
+ catch (_b) {
4601
+ return undefined;
4602
+ }
4603
+ }
4542
4604
  /** Short, non-secret fingerprint used only to key the in-flight map. Never logged. */
4543
4605
  function tokenFingerprint(token) {
4544
4606
  let h = 5381;
@@ -4667,6 +4729,12 @@ async function getFreshAuthToken(isServer = false, options = {}) {
4667
4729
  // it is valid right now, so use it rather than failing the request.
4668
4730
  if (stillUsable)
4669
4731
  return idToken;
4732
+ // ...but not forever: past its expiry plus the same grace the restore path
4733
+ // allows, a session that still cannot refresh is dropped, so an open tab stops
4734
+ // asking an issuer that will never answer it.
4735
+ if (!mayRetainAfterTransportFailure(tokenExpSeconds(idToken))) {
4736
+ await clearDeadSession(isServer, generation);
4737
+ }
4670
4738
  throw makeAuthExpiredError('Authentication expired and refresh failed', status);
4671
4739
  }
4672
4740
  finally {
@@ -7168,7 +7236,11 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
7168
7236
  }, matchesThisSubscription);
7169
7237
  connection = acquisition.connection;
7170
7238
  releaseConnection = acquisition.release;
7171
- existingSubscription = existing;
7239
+ // Identical subscribes started together all scan before any of them registers,
7240
+ // so look again here, synchronously with the registration below: whichever
7241
+ // registered first is joined. Otherwise each sends its own id for one spec,
7242
+ // and the worker refuses past one subscription plus eight aliases.
7243
+ existingSubscription = existing !== null && existing !== void 0 ? existing : Array.from(connection.subscriptions.values()).find(matchesThisSubscription);
7172
7244
  }
7173
7245
  catch (error) {
7174
7246
  const err = error instanceof Error ? error : new Error(String(error));
@@ -7529,7 +7601,7 @@ async function doReconnectWithNewAuth() {
7529
7601
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
7530
7602
  }
7531
7603
  try {
7532
- const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-7itLaAao.js'); });
7604
+ const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-BiChlioj.js'); });
7533
7605
  await reconnectRealtimeStoreWithNewAuth();
7534
7606
  }
7535
7607
  catch (error) {
@@ -8907,7 +8979,7 @@ async function set(path, document, options) {
8907
8979
  * state appears only under `observation`/`mirrorSync` once actually observed.
8908
8980
  */
8909
8981
  async function setMany(many, options) {
8910
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
8982
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
8911
8983
  // Returns the data that was set, or undefined if the document was already set.
8912
8984
  try {
8913
8985
  // A server WalletClient threads its immutable config snapshot; the default
@@ -9021,13 +9093,15 @@ async function setMany(many, options) {
9021
9093
  throw new Error('The write response carried no execution-chain tag, so this receipt could not state where the write ran. ' +
9022
9094
  'Nothing was signed or submitted.');
9023
9095
  }
9096
+ const grantLane = (_l = options === null || options === void 0 ? void 0 : options._overrides) === null || _l === void 0 ? void 0 : _l._grantLane;
9097
+ grantLane === null || grantLane === void 0 ? void 0 : grantLane.assertLane(workerChain);
9024
9098
  if (workerChain === 'realtime_offchain') {
9025
9099
  // Poofnet: the wallet signs the worker's unsendable approval message and
9026
9100
  // the worker verifies and commits it. Nothing is submitted to any chain.
9027
9101
  if (typeof curTx.serializedTransaction !== 'string' || typeof curTx.poofnetIntentId !== 'string') {
9028
9102
  throw new Error('Unsupported 202 transaction shape: a poofnet write needs serializedTransaction and poofnetIntentId');
9029
9103
  }
9030
- const { signPoofnetTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-CNeO_lJ2.js'); });
9104
+ const { signPoofnetTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-B3u6Arbf.js'); });
9031
9105
  const signedTransaction = await signPoofnetTransaction(curTx.serializedTransaction, authProvider, options, buildExpectedWriteIntent(documents));
9032
9106
  // Signing-only, exactly as on a real chain: signed and NOT committed.
9033
9107
  if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
@@ -9037,12 +9111,25 @@ async function setMany(many, options) {
9037
9111
  poofnetIntentId: curTx.poofnetIntentId,
9038
9112
  };
9039
9113
  }
9040
- // The commit PUT is the one ambiguous step of this lane: a lost response
9041
- // may follow a commit, so its failures stay untagged (read the path back).
9114
+ // A grant lane's first credential is spent by the PUT that built this approval, so the
9115
+ // commit's is minted now, before `sending`: failing to mint sends nothing.
9116
+ const commitHeaders = await (grantLane === null || grantLane === void 0 ? void 0 : grantLane.commitHeaders());
9117
+ // The commit PUT is the one ambiguous step of this lane: a lost response may follow a
9118
+ // commit, so a transport failure or a 5xx stays untagged (read the path back). A 4xx is
9119
+ // realtime stating it committed nothing (the intent expired or was taken, the signer did
9120
+ // not match, a rule refused it), as for the first PUT. 408 and 499 are a proxy's words,
9121
+ // not realtime's, and may follow a commit, so they stay ambiguous too.
9042
9122
  sending = true;
9043
- const commitResponse = await makeApiRequest('PUT', 'items/signed', { intentId: curTx.poofnetIntentId, signedTransaction }, options === null || options === void 0 ? void 0 : options._overrides);
9044
- if (commitResponse.status !== 200) {
9045
- throw new Error(`Poofnet signed write was not committed (status ${commitResponse.status})`);
9123
+ let commitResponse;
9124
+ try {
9125
+ commitResponse = await makeApiRequest('PUT', 'items/signed', { intentId: curTx.poofnetIntentId, signedTransaction }, commitHeaders ? Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options._overrides), { _getAuthHeaders: async () => commitHeaders }) : options === null || options === void 0 ? void 0 : options._overrides);
9126
+ if (commitResponse.status !== 200) {
9127
+ throw Object.assign(new Error(`Poofnet signed write was not committed (status ${commitResponse.status})`), { statusCode: commitResponse.status });
9128
+ }
9129
+ }
9130
+ catch (error) {
9131
+ const status = Number(error === null || error === void 0 ? void 0 : error.statusCode);
9132
+ throw status >= 400 && status < 500 && status !== 408 && status !== 499 ? markWriteNotApplied(error, 'broadcast_rejected') : error;
9046
9133
  }
9047
9134
  return directCommitResult(commitResponse, documents);
9048
9135
  }
@@ -9052,7 +9139,7 @@ async function setMany(many, options) {
9052
9139
  'Nothing was signed or submitted.');
9053
9140
  }
9054
9141
  const solanaChain = workerChain;
9055
- const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-CNeO_lJ2.js'); });
9142
+ const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-B3u6Arbf.js'); });
9056
9143
  // OA-0001: derive the expected intent from OUR OWN request (the one
9057
9144
  // ground truth a tampered builder response cannot change) so the
9058
9145
  // validator can reject any transaction whose operation semantics
@@ -9091,7 +9178,9 @@ async function setMany(many, options) {
9091
9178
  }
9092
9179
  // Confirmed: the receipt is already complete, so the mirror read is a
9093
9180
  // best-effort enrichment reported in `mirrorSync`, never a rejection.
9094
- const mirrorSync = await attemptMirrorSync(requestedPaths, options);
9181
+ const mirrorSync = grantLane
9182
+ ? { observed: false, reason: 'not attempted: a venue grant authorizes only the write' }
9183
+ : await attemptMirrorSync(requestedPaths, options);
9095
9184
  return {
9096
9185
  status: 'confirmed', appId, requestedDocuments, chain: solanaChain,
9097
9186
  transactionId: solanaOutcome.transactionSignature,
@@ -10114,6 +10203,84 @@ function createAppTokenAuthHeaders(options) {
10114
10203
  };
10115
10204
  }
10116
10205
 
10206
+ /**
10207
+ * The venue grant write lane: create ONE document in another app (an OpenApps venue) as the
10208
+ * wallet signed in here, authorized by a single-use grant instead of a venue session
10209
+ * (VENUE-GRANT-DESIGN.md). The grant only lets the venue build that one transaction for that
10210
+ * wallet; the wallet's own signature, checked by the venue's own rule, is the authority.
10211
+ *
10212
+ * The grant itself is minted by Bounded Auth at `POST {humanAuthApiUrl}/app-token/venue-grant`
10213
+ * (contract: packages/cdk/cloudflare/shared/venue-grant.ts, which this package cannot import
10214
+ * because the SDK packages publish separately). This module only spends one.
10215
+ *
10216
+ * It reuses `setMany`'s request and 202 handling through `_overrides`: the `_config` snapshot is
10217
+ * this app's own config re-pointed at the VENUE app id, so the pre-sign validator (OA-0001) binds
10218
+ * the venue app id and the full expected intent, and the signer stays this app's own AuthProvider
10219
+ * (a Turnkey session or a Wallet Standard wallet). Nothing here is reachable from `createClient()`,
10220
+ * which stays read-only.
10221
+ *
10222
+ * The chain a write runs on is the VENUE's, stated by its 202, never the app's: the app's own
10223
+ * `chain` can only agree with it or be refused, and the transaction is broadcast through the app's
10224
+ * `rpcUrl` only when the app configured that same chain, otherwise through `resolveRpcUrl(chain)`.
10225
+ */
10226
+ function laneError(code, message) {
10227
+ return Object.assign(new Error(message), { code });
10228
+ }
10229
+ const isSolanaLane = (chain) => chain === 'solana_devnet' || chain === 'solana_mainnet';
10230
+ /**
10231
+ * The RPC a venue write on Solana lane `chain` is broadcast through, and a saved receipt of it is
10232
+ * reconciled through. The app's own `rpcUrl` counts only when the app also configured this chain
10233
+ * (an `rpcUrl` alone does not say which chain it speaks); otherwise `resolveRpcUrl(chain)`. Null
10234
+ * when neither yields one.
10235
+ *
10236
+ * @internal Shared by the lane and by the client SDK's reconciliation of a saved receipt.
10237
+ */
10238
+ function resolveVenueRpcUrl(config, chain, resolveRpcUrl) {
10239
+ var _a, _b;
10240
+ const own = requiredSolanaChain(config) === chain ? (_a = config.rpcUrl) === null || _a === void 0 ? void 0 : _a.trim() : '';
10241
+ return own || ((_b = resolveRpcUrl === null || resolveRpcUrl === void 0 ? void 0 : resolveRpcUrl(chain)) === null || _b === void 0 ? void 0 : _b.trim()) || null;
10242
+ }
10243
+ /**
10244
+ * Refuse, before the wallet is asked to sign, a 202 whose execution lane this app is not
10245
+ * configured for: a mainnet app must never sign a devnet or poofnet approval (a poofnet "funding"
10246
+ * moves no real USDC). Then point the Solana lane's broadcast at the RPC for the venue's chain.
10247
+ * Poofnet broadcasts nothing, so it needs no RPC.
10248
+ */
10249
+ function assertExecutionLane(config, chain, options) {
10250
+ const required = requiredSolanaChain(config);
10251
+ if (required && chain !== required) {
10252
+ throw laneError('chain_mismatch', `The venue answered on "${chain}" but this app is configured for "${config.chain}". Nothing was signed.`);
10253
+ }
10254
+ if (!isSolanaLane(chain))
10255
+ return;
10256
+ const rpcUrl = resolveVenueRpcUrl(config, chain, options.resolveRpcUrl);
10257
+ if (rpcUrl)
10258
+ config.rpcUrl = rpcUrl;
10259
+ else if (!options._solanaRpc) {
10260
+ throw laneError('chain_not_configured', `The venue runs on ${chain}, but no RPC could be resolved to broadcast through. ` +
10261
+ `Pass chain: '${chain}' and an rpcUrl to init(). Nothing was signed.`);
10262
+ }
10263
+ }
10264
+ /**
10265
+ * Write `document` at `path` in the venue `venueAppId` with a single-use grant. Resolves to the
10266
+ * same discriminated {@link SetResult} as `set`, and throws exactly as `set` does, so the same
10267
+ * `isWriteNotApplied` reading applies.
10268
+ *
10269
+ * @internal Used by `fundVenueEscrow` and `disputeVenueEscrow` in @bounded-sh/client. Not a stable API.
10270
+ */
10271
+ async function setWithVenueGrant(path, document, options) {
10272
+ // A private copy of this app's config for this one write: the lane fills in its RPC below.
10273
+ const config = Object.assign(Object.assign({}, (await getConfig())), { appId: options.venueAppId });
10274
+ const headers = (grant) => ({ Authorization: `Bearer ${grant}` });
10275
+ return setMany([{ path, document }], {
10276
+ onBroadcast: options.onBroadcast,
10277
+ _overrides: Object.assign({ _config: config, _getAuthHeaders: async () => headers(options.grant), _grantLane: {
10278
+ assertLane: (chain) => assertExecutionLane(config, chain, options),
10279
+ commitHeaders: async () => headers(await options.commitGrant()),
10280
+ } }, (options._solanaRpc ? { _solanaRpc: options._solanaRpc } : {})),
10281
+ });
10282
+ }
10283
+
10117
10284
  /**
10118
10285
  * Live named-query subscriptions (subscribeQuery) - HTTP client poller (v1).
10119
10286
  *
@@ -10816,7 +10983,7 @@ function toMillis(seconds) {
10816
10983
 
10817
10984
  /** SIWS message creation is async already, so its onchain module can be lazy. */
10818
10985
  async function genSolanaMessage(address, nonce) {
10819
- const implementation = await Promise.resolve().then(function () { return require('./solana-message-Ce6_jR6H.js'); });
10986
+ const implementation = await Promise.resolve().then(function () { return require('./solana-message-BVwD6WVm.js'); });
10820
10987
  return implementation.genSolanaMessage(address, nonce);
10821
10988
  }
10822
10989
 
@@ -11440,6 +11607,7 @@ exports.queryAggregate = queryAggregate;
11440
11607
  exports.reconcileSetResult = reconcileSetResult;
11441
11608
  exports.reconnectWithNewAuth = reconnectWithNewAuth;
11442
11609
  exports.refreshSession = refreshSession;
11610
+ exports.resolveVenueRpcUrl = resolveVenueRpcUrl;
11443
11611
  exports.revokeSession = revokeSession;
11444
11612
  exports.runExpression = runExpression;
11445
11613
  exports.runExpressionMany = runExpressionMany;
@@ -11450,6 +11618,7 @@ exports.serverTimestamp = serverTimestamp;
11450
11618
  exports.set = set;
11451
11619
  exports.setFile = setFile;
11452
11620
  exports.setMany = setMany;
11621
+ exports.setWithVenueGrant = setWithVenueGrant;
11453
11622
  exports.signAndSubmitTransaction = signAndSubmitTransaction;
11454
11623
  exports.signMessage = signMessage;
11455
11624
  exports.signSessionCreateMessage = signSessionCreateMessage;
@@ -11464,4 +11633,4 @@ exports.toSeconds = toSeconds;
11464
11633
  exports.waitForSetResult = waitForSetResult;
11465
11634
  exports.withEffects = withEffects;
11466
11635
  exports.writeNotAppliedPhase = writeNotAppliedPhase;
11467
- //# sourceMappingURL=index-WIHuQp4L.js.map
11636
+ //# sourceMappingURL=index-5TMFraRK.js.map