@bounded-sh/core 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.
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--ahzzmm5.js → index-5TMFraRK.js} +225 -60
  6. package/dist/index-5TMFraRK.js.map +1 -0
  7. package/dist/{index-DTTh5Wt5.mjs → index-BilGFb3V.mjs} +224 -61
  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-Bcra04TH.mjs → realtime-store-Bc71mTWr.mjs} +2 -2
  14. package/dist/{realtime-store-Bcra04TH.mjs.map → realtime-store-Bc71mTWr.mjs.map} +1 -1
  15. package/dist/{realtime-store-CR80Jac5.js → realtime-store-BiChlioj.js} +2 -2
  16. package/dist/{realtime-store-CR80Jac5.js.map → realtime-store-BiChlioj.js.map} +1 -1
  17. package/dist/{solana-message-BwPW9Fst.mjs → solana-message-BNGEBst_.mjs} +2 -2
  18. package/dist/{solana-message-BwPW9Fst.mjs.map → solana-message-BNGEBst_.mjs.map} +1 -1
  19. package/dist/{solana-message-DPR9uw0N.js → solana-message-BVwD6WVm.js} +2 -2
  20. package/dist/{solana-message-DPR9uw0N.js.map → solana-message-BVwD6WVm.js.map} +1 -1
  21. package/dist/{solana-operations-BMB6sq_i.js → solana-operations-B3u6Arbf.js} +3 -3
  22. package/dist/{solana-operations-BMB6sq_i.js.map → solana-operations-B3u6Arbf.js.map} +1 -1
  23. package/dist/{solana-operations-BBQllFVm.mjs → solana-operations-BLbRO1rE.mjs} +3 -3
  24. package/dist/{solana-operations-BBQllFVm.mjs.map → solana-operations-BLbRO1rE.mjs.map} +1 -1
  25. package/dist/{solana-v1-operations-CCHbLEo-.js → solana-v1-operations-BxTmxUMN.js} +3 -3
  26. package/dist/{solana-v1-operations-CCHbLEo-.js.map → solana-v1-operations-BxTmxUMN.js.map} +1 -1
  27. package/dist/{solana-v1-operations-DBuA0PlD.mjs → solana-v1-operations-b1rmLBUP.mjs} +3 -3
  28. package/dist/{solana-v1-operations-DBuA0PlD.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--ahzzmm5.js.map +0 -1
  32. package/dist/index-DTTh5Wt5.mjs.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-DPR9uw0N.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 {
@@ -7533,7 +7601,7 @@ async function doReconnectWithNewAuth() {
7533
7601
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
7534
7602
  }
7535
7603
  try {
7536
- const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-CR80Jac5.js'); });
7604
+ const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-BiChlioj.js'); });
7537
7605
  await reconnectRealtimeStoreWithNewAuth();
7538
7606
  }
7539
7607
  catch (error) {
@@ -8911,7 +8979,7 @@ async function set(path, document, options) {
8911
8979
  * state appears only under `observation`/`mirrorSync` once actually observed.
8912
8980
  */
8913
8981
  async function setMany(many, options) {
8914
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
8982
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
8915
8983
  // Returns the data that was set, or undefined if the document was already set.
8916
8984
  try {
8917
8985
  // A server WalletClient threads its immutable config snapshot; the default
@@ -9025,13 +9093,15 @@ async function setMany(many, options) {
9025
9093
  throw new Error('The write response carried no execution-chain tag, so this receipt could not state where the write ran. ' +
9026
9094
  'Nothing was signed or submitted.');
9027
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);
9028
9098
  if (workerChain === 'realtime_offchain') {
9029
9099
  // Poofnet: the wallet signs the worker's unsendable approval message and
9030
9100
  // the worker verifies and commits it. Nothing is submitted to any chain.
9031
9101
  if (typeof curTx.serializedTransaction !== 'string' || typeof curTx.poofnetIntentId !== 'string') {
9032
9102
  throw new Error('Unsupported 202 transaction shape: a poofnet write needs serializedTransaction and poofnetIntentId');
9033
9103
  }
9034
- const { signPoofnetTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-BMB6sq_i.js'); });
9104
+ const { signPoofnetTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-B3u6Arbf.js'); });
9035
9105
  const signedTransaction = await signPoofnetTransaction(curTx.serializedTransaction, authProvider, options, buildExpectedWriteIntent(documents));
9036
9106
  // Signing-only, exactly as on a real chain: signed and NOT committed.
9037
9107
  if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
@@ -9041,12 +9111,25 @@ async function setMany(many, options) {
9041
9111
  poofnetIntentId: curTx.poofnetIntentId,
9042
9112
  };
9043
9113
  }
9044
- // The commit PUT is the one ambiguous step of this lane: a lost response
9045
- // 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.
9046
9122
  sending = true;
9047
- const commitResponse = await makeApiRequest('PUT', 'items/signed', { intentId: curTx.poofnetIntentId, signedTransaction }, options === null || options === void 0 ? void 0 : options._overrides);
9048
- if (commitResponse.status !== 200) {
9049
- 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;
9050
9133
  }
9051
9134
  return directCommitResult(commitResponse, documents);
9052
9135
  }
@@ -9056,7 +9139,7 @@ async function setMany(many, options) {
9056
9139
  'Nothing was signed or submitted.');
9057
9140
  }
9058
9141
  const solanaChain = workerChain;
9059
- const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-BMB6sq_i.js'); });
9142
+ const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-B3u6Arbf.js'); });
9060
9143
  // OA-0001: derive the expected intent from OUR OWN request (the one
9061
9144
  // ground truth a tampered builder response cannot change) so the
9062
9145
  // validator can reject any transaction whose operation semantics
@@ -9095,7 +9178,9 @@ async function setMany(many, options) {
9095
9178
  }
9096
9179
  // Confirmed: the receipt is already complete, so the mirror read is a
9097
9180
  // best-effort enrichment reported in `mirrorSync`, never a rejection.
9098
- 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);
9099
9184
  return {
9100
9185
  status: 'confirmed', appId, requestedDocuments, chain: solanaChain,
9101
9186
  transactionId: solanaOutcome.transactionSignature,
@@ -10118,6 +10203,84 @@ function createAppTokenAuthHeaders(options) {
10118
10203
  };
10119
10204
  }
10120
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
+
10121
10284
  /**
10122
10285
  * Live named-query subscriptions (subscribeQuery) - HTTP client poller (v1).
10123
10286
  *
@@ -10820,7 +10983,7 @@ function toMillis(seconds) {
10820
10983
 
10821
10984
  /** SIWS message creation is async already, so its onchain module can be lazy. */
10822
10985
  async function genSolanaMessage(address, nonce) {
10823
- const implementation = await Promise.resolve().then(function () { return require('./solana-message-DPR9uw0N.js'); });
10986
+ const implementation = await Promise.resolve().then(function () { return require('./solana-message-BVwD6WVm.js'); });
10824
10987
  return implementation.genSolanaMessage(address, nonce);
10825
10988
  }
10826
10989
 
@@ -11444,6 +11607,7 @@ exports.queryAggregate = queryAggregate;
11444
11607
  exports.reconcileSetResult = reconcileSetResult;
11445
11608
  exports.reconnectWithNewAuth = reconnectWithNewAuth;
11446
11609
  exports.refreshSession = refreshSession;
11610
+ exports.resolveVenueRpcUrl = resolveVenueRpcUrl;
11447
11611
  exports.revokeSession = revokeSession;
11448
11612
  exports.runExpression = runExpression;
11449
11613
  exports.runExpressionMany = runExpressionMany;
@@ -11454,6 +11618,7 @@ exports.serverTimestamp = serverTimestamp;
11454
11618
  exports.set = set;
11455
11619
  exports.setFile = setFile;
11456
11620
  exports.setMany = setMany;
11621
+ exports.setWithVenueGrant = setWithVenueGrant;
11457
11622
  exports.signAndSubmitTransaction = signAndSubmitTransaction;
11458
11623
  exports.signMessage = signMessage;
11459
11624
  exports.signSessionCreateMessage = signSessionCreateMessage;
@@ -11468,4 +11633,4 @@ exports.toSeconds = toSeconds;
11468
11633
  exports.waitForSetResult = waitForSetResult;
11469
11634
  exports.withEffects = withEffects;
11470
11635
  exports.writeNotAppliedPhase = writeNotAppliedPhase;
11471
- //# sourceMappingURL=index--ahzzmm5.js.map
11636
+ //# sourceMappingURL=index-5TMFraRK.js.map