@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
@@ -419,6 +419,97 @@ axiosRetry.exponentialDelay = exponentialDelay;
419
419
  axiosRetry.linearDelay = linearDelay;
420
420
  axiosRetry.isRetryableError = isRetryableError;
421
421
 
422
+ // Classify a session-refresh failure as a DEFINITIVE rejection of the credential
423
+ // vs a TRANSPORT failure (DS4-0054).
424
+ //
425
+ // The restore path refreshes an expired token at boot. The old code destroyed the
426
+ // local session on ANY refresh failure, so a network blip - common on mobile -
427
+ // signed the user out. Only a real HTTP rejection of the refresh token proves the
428
+ // session is invalid; anything else is unknown, and the safe move is to RETAIN local
429
+ // state and retry. Keeping a genuinely dead session locally is harmless: every
430
+ // request still fails closed server-side, and the next refresh that reaches the
431
+ // server clears it. Destroying a live session on a blip is user-visible damage.
432
+ //
433
+ // `/session/refresh` answers 401 (session_revoked / invalid_grant) when the token is
434
+ // dead. 400 (bad request), 408/429 (transient), and 5xx are NOT proof the session is
435
+ // invalid, and a network/DNS/timeout error carries no HTTP response at all.
436
+ //
437
+ // This is the single classifier for every refresh path: the per-request freshness
438
+ // check (auth-token-freshness) and the boot-time restore paths (web + RN session
439
+ // managers) all import it, so "definitive" means the same thing everywhere. It lives
440
+ // in this leaf module to avoid an import cycle between those consumers.
441
+ function isDefinitiveRejection(err) {
442
+ var _a;
443
+ const status = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status;
444
+ return status === 401 || status === 403;
445
+ }
446
+ // How long past the id token's own `exp` a session may survive purely on
447
+ // transport failures.
448
+ //
449
+ // "Retain on a blip" must not become "retain forever". A statusless failure is
450
+ // unknown, so the first ones are absorbed - but a revoked session, or a gateway
451
+ // that rewrites the issuer's 401 into a 502, would otherwise keep
452
+ // isAuthenticated() reporting a signed-in user for as long as the device never
453
+ // reaches the issuer again. One day covers an offline flight or a bad-network day
454
+ // while making the retention finite: past it, an unrefreshable session is cleared
455
+ // and the user is asked to sign in again.
456
+ const TRANSPORT_RETENTION_GRACE_MS = 24 * 60 * 60 * 1000;
457
+ /**
458
+ * May a session whose refresh failed WITHOUT a definitive rejection be kept?
459
+ *
460
+ * `expSeconds` is the id token's `exp` claim. Fails closed: a missing or
461
+ * non-numeric expiry is not a usable bound, so such a session is never retained.
462
+ */
463
+ function mayRetainAfterTransportFailure(expSeconds, now = Date.now()) {
464
+ if (typeof expSeconds !== 'number' || !Number.isFinite(expSeconds))
465
+ return false;
466
+ return now <= expSeconds * 1000 + TRANSPORT_RETENTION_GRACE_MS;
467
+ }
468
+ // Backoff between refresh attempts that failed WITHOUT a definitive rejection.
469
+ //
470
+ // A transport failure retains the session (above), and every authenticated request
471
+ // and the realtime token timer asks for a fresh token - so without a pause, an issuer
472
+ // that answers 503 or 429 gets one refresh per request from every open tab, which is
473
+ // the exact load that keeps it down. After such a failure the same refresh token is
474
+ // not sent again until the backoff passes: 15s, doubling, capped at 10 minutes, or the
475
+ // issuer's own Retry-After when it is longer. Callers inside the window get a
476
+ // non-definitive error at once (`code: 'refresh_backoff'`) and treat it like the
477
+ // failure that started it. A success, or a different token, starts clean.
478
+ const REFRESH_BACKOFF_BASE_MS = 15000;
479
+ const REFRESH_BACKOFF_MAX_MS = 10 * 60000;
480
+ const refreshBackoff = new Map();
481
+ function retryAfterMs(err) {
482
+ var _a, _b;
483
+ const headers = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.headers;
484
+ const raw = headers && ((_b = headers['retry-after']) !== null && _b !== void 0 ? _b : headers['Retry-After']);
485
+ const seconds = Number(raw);
486
+ return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds * 1000, REFRESH_BACKOFF_MAX_MS) : 0;
487
+ }
488
+ /** Throws a non-definitive `refresh_backoff` error while `key` is still backing off. */
489
+ function assertRefreshAllowed(key, now = Date.now()) {
490
+ const entry = refreshBackoff.get(key);
491
+ if (!entry || now >= entry.until)
492
+ return;
493
+ const err = new Error('Session refresh is backing off after a failed attempt');
494
+ err.code = 'refresh_backoff';
495
+ err.retryAt = entry.until;
496
+ throw err;
497
+ }
498
+ /** Record the outcome of a refresh attempt for `key`. */
499
+ function recordRefreshOutcome(key, err, now = Date.now()) {
500
+ var _a, _b;
501
+ if (!err || isDefinitiveRejection(err)) {
502
+ refreshBackoff.delete(key);
503
+ return;
504
+ }
505
+ const failures = ((_b = (_a = refreshBackoff.get(key)) === null || _a === void 0 ? void 0 : _a.failures) !== null && _b !== void 0 ? _b : 0) + 1;
506
+ const backoff = Math.min(REFRESH_BACKOFF_BASE_MS * 2 ** (failures - 1), REFRESH_BACKOFF_MAX_MS);
507
+ refreshBackoff.set(key, { failures, until: now + Math.max(backoff, retryAfterMs(err)) });
508
+ // One entry per refresh token; a long-lived tab rotates through a handful at most.
509
+ if (refreshBackoff.size > 32)
510
+ refreshBackoff.delete(refreshBackoff.keys().next().value);
511
+ }
512
+
422
513
  /**
423
514
  * Resolve the refresh/revoke base URL to a CONFIGURED issuer only. The session's
424
515
  * `issuer` is persisted in client storage; if that storage is tampered (e.g. XSS)
@@ -572,6 +663,9 @@ async function refreshSession(refreshToken, issuer) {
572
663
  const existing = refreshInFlightByToken.get(dedupeKey);
573
664
  if (existing)
574
665
  return existing;
666
+ // Inside a backoff window this throws at once, so a failing issuer is not asked again
667
+ // by every request and timer in the page (see refresh-error.ts).
668
+ assertRefreshAllowed(dedupeKey);
575
669
  let currentFlight;
576
670
  currentFlight = (async () => {
577
671
  try {
@@ -585,8 +679,13 @@ async function refreshSession(refreshToken, issuer) {
585
679
  refreshToken,
586
680
  appId
587
681
  }, { headers: { 'Content-Type': 'application/json' }, timeout: 30000 });
682
+ recordRefreshOutcome(dedupeKey, null);
588
683
  return response.data;
589
684
  }
685
+ catch (error) {
686
+ recordRefreshOutcome(dedupeKey, error);
687
+ throw error;
688
+ }
590
689
  finally {
591
690
  // Only remove our own entry, and only if it is still the active flight
592
691
  // for this token (a later refresh of the same token cannot have replaced
@@ -644,53 +743,6 @@ async function revokeSession(refreshToken, issuer) {
644
743
  async function signSessionCreateMessage(_signMessageFunction) {
645
744
  }
646
745
 
647
- // Classify a session-refresh failure as a DEFINITIVE rejection of the credential
648
- // vs a TRANSPORT failure (DS4-0054).
649
- //
650
- // The restore path refreshes an expired token at boot. The old code destroyed the
651
- // local session on ANY refresh failure, so a network blip - common on mobile -
652
- // signed the user out. Only a real HTTP rejection of the refresh token proves the
653
- // session is invalid; anything else is unknown, and the safe move is to RETAIN local
654
- // state and retry. Keeping a genuinely dead session locally is harmless: every
655
- // request still fails closed server-side, and the next refresh that reaches the
656
- // server clears it. Destroying a live session on a blip is user-visible damage.
657
- //
658
- // `/session/refresh` answers 401 (session_revoked / invalid_grant) when the token is
659
- // dead. 400 (bad request), 408/429 (transient), and 5xx are NOT proof the session is
660
- // invalid, and a network/DNS/timeout error carries no HTTP response at all.
661
- //
662
- // This is the single classifier for every refresh path: the per-request freshness
663
- // check (auth-token-freshness) and the boot-time restore paths (web + RN session
664
- // managers) all import it, so "definitive" means the same thing everywhere. It lives
665
- // in this leaf module to avoid an import cycle between those consumers.
666
- function isDefinitiveRejection(err) {
667
- var _a;
668
- const status = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status;
669
- return status === 401 || status === 403;
670
- }
671
- // How long past the id token's own `exp` a session may survive purely on
672
- // transport failures.
673
- //
674
- // "Retain on a blip" must not become "retain forever". A statusless failure is
675
- // unknown, so the first ones are absorbed - but a revoked session, or a gateway
676
- // that rewrites the issuer's 401 into a 502, would otherwise keep
677
- // isAuthenticated() reporting a signed-in user for as long as the device never
678
- // reaches the issuer again. One day covers an offline flight or a bad-network day
679
- // while making the retention finite: past it, an unrefreshable session is cleared
680
- // and the user is asked to sign in again.
681
- const TRANSPORT_RETENTION_GRACE_MS = 24 * 60 * 60 * 1000;
682
- /**
683
- * May a session whose refresh failed WITHOUT a definitive rejection be kept?
684
- *
685
- * `expSeconds` is the id token's `exp` claim. Fails closed: a missing or
686
- * non-numeric expiry is not a usable bound, so such a session is never retained.
687
- */
688
- function mayRetainAfterTransportFailure(expSeconds, now = Date.now()) {
689
- if (typeof expSeconds !== 'number' || !Number.isFinite(expSeconds))
690
- return false;
691
- return now <= expSeconds * 1000 + TRANSPORT_RETENTION_GRACE_MS;
692
- }
693
-
694
746
  // Shared session-binding guard for the web + React Native session managers, so
695
747
  // the cross-principal refresh guard (#220 / #221) lives in ONE place and the two
696
748
  // managers can't drift apart. Base64url decoding differs by platform (browser
@@ -4188,7 +4240,7 @@ class ServerSessionManager {
4188
4240
  const sessionConfig = (_a = this.sessionConfig) !== null && _a !== void 0 ? _a : undefined;
4189
4241
  /* fetch nonce from auth API (bound to the snapshotted app+issuer) */
4190
4242
  const nonce = await genAuthNonce(sessionConfig);
4191
- const { genSolanaMessage } = await import('./solana-message-BBMxFFjM.mjs');
4243
+ const { genSolanaMessage } = await import('./solana-message-BNGEBst_.mjs');
4192
4244
  const message = await genSolanaMessage(address, nonce, sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId);
4193
4245
  /* sign the message */
4194
4246
  const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
@@ -4537,6 +4589,16 @@ function classifyToken(token, nowMs = Date.now(), skewMs = TOKEN_EXPIRY_SKEW_MS)
4537
4589
  return 'expiring-soon';
4538
4590
  return 'fresh';
4539
4591
  }
4592
+ function tokenExpSeconds(token) {
4593
+ var _a;
4594
+ try {
4595
+ const exp = (_a = JSON.parse(decodeBase64Url(token.split('.')[1]))) === null || _a === void 0 ? void 0 : _a.exp;
4596
+ return typeof exp === 'number' ? exp : undefined;
4597
+ }
4598
+ catch (_b) {
4599
+ return undefined;
4600
+ }
4601
+ }
4540
4602
  /** Short, non-secret fingerprint used only to key the in-flight map. Never logged. */
4541
4603
  function tokenFingerprint(token) {
4542
4604
  let h = 5381;
@@ -4665,6 +4727,12 @@ async function getFreshAuthToken(isServer = false, options = {}) {
4665
4727
  // it is valid right now, so use it rather than failing the request.
4666
4728
  if (stillUsable)
4667
4729
  return idToken;
4730
+ // ...but not forever: past its expiry plus the same grace the restore path
4731
+ // allows, a session that still cannot refresh is dropped, so an open tab stops
4732
+ // asking an issuer that will never answer it.
4733
+ if (!mayRetainAfterTransportFailure(tokenExpSeconds(idToken))) {
4734
+ await clearDeadSession(isServer, generation);
4735
+ }
4668
4736
  throw makeAuthExpiredError('Authentication expired and refresh failed', status);
4669
4737
  }
4670
4738
  finally {
@@ -7166,7 +7234,11 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
7166
7234
  }, matchesThisSubscription);
7167
7235
  connection = acquisition.connection;
7168
7236
  releaseConnection = acquisition.release;
7169
- existingSubscription = existing;
7237
+ // Identical subscribes started together all scan before any of them registers,
7238
+ // so look again here, synchronously with the registration below: whichever
7239
+ // registered first is joined. Otherwise each sends its own id for one spec,
7240
+ // and the worker refuses past one subscription plus eight aliases.
7241
+ existingSubscription = existing !== null && existing !== void 0 ? existing : Array.from(connection.subscriptions.values()).find(matchesThisSubscription);
7170
7242
  }
7171
7243
  catch (error) {
7172
7244
  const err = error instanceof Error ? error : new Error(String(error));
@@ -7527,7 +7599,7 @@ async function doReconnectWithNewAuth() {
7527
7599
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
7528
7600
  }
7529
7601
  try {
7530
- const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-BIB8v265.mjs');
7602
+ const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-Bc71mTWr.mjs');
7531
7603
  await reconnectRealtimeStoreWithNewAuth();
7532
7604
  }
7533
7605
  catch (error) {
@@ -8905,7 +8977,7 @@ async function set(path, document, options) {
8905
8977
  * state appears only under `observation`/`mirrorSync` once actually observed.
8906
8978
  */
8907
8979
  async function setMany(many, options) {
8908
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
8980
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
8909
8981
  // Returns the data that was set, or undefined if the document was already set.
8910
8982
  try {
8911
8983
  // A server WalletClient threads its immutable config snapshot; the default
@@ -9019,13 +9091,15 @@ async function setMany(many, options) {
9019
9091
  throw new Error('The write response carried no execution-chain tag, so this receipt could not state where the write ran. ' +
9020
9092
  'Nothing was signed or submitted.');
9021
9093
  }
9094
+ const grantLane = (_l = options === null || options === void 0 ? void 0 : options._overrides) === null || _l === void 0 ? void 0 : _l._grantLane;
9095
+ grantLane === null || grantLane === void 0 ? void 0 : grantLane.assertLane(workerChain);
9022
9096
  if (workerChain === 'realtime_offchain') {
9023
9097
  // Poofnet: the wallet signs the worker's unsendable approval message and
9024
9098
  // the worker verifies and commits it. Nothing is submitted to any chain.
9025
9099
  if (typeof curTx.serializedTransaction !== 'string' || typeof curTx.poofnetIntentId !== 'string') {
9026
9100
  throw new Error('Unsupported 202 transaction shape: a poofnet write needs serializedTransaction and poofnetIntentId');
9027
9101
  }
9028
- const { signPoofnetTransaction, buildExpectedWriteIntent } = await import('./solana-operations-Cshh2EfH.mjs');
9102
+ const { signPoofnetTransaction, buildExpectedWriteIntent } = await import('./solana-operations-BLbRO1rE.mjs');
9029
9103
  const signedTransaction = await signPoofnetTransaction(curTx.serializedTransaction, authProvider, options, buildExpectedWriteIntent(documents));
9030
9104
  // Signing-only, exactly as on a real chain: signed and NOT committed.
9031
9105
  if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
@@ -9035,12 +9109,25 @@ async function setMany(many, options) {
9035
9109
  poofnetIntentId: curTx.poofnetIntentId,
9036
9110
  };
9037
9111
  }
9038
- // The commit PUT is the one ambiguous step of this lane: a lost response
9039
- // may follow a commit, so its failures stay untagged (read the path back).
9112
+ // A grant lane's first credential is spent by the PUT that built this approval, so the
9113
+ // commit's is minted now, before `sending`: failing to mint sends nothing.
9114
+ const commitHeaders = await (grantLane === null || grantLane === void 0 ? void 0 : grantLane.commitHeaders());
9115
+ // The commit PUT is the one ambiguous step of this lane: a lost response may follow a
9116
+ // commit, so a transport failure or a 5xx stays untagged (read the path back). A 4xx is
9117
+ // realtime stating it committed nothing (the intent expired or was taken, the signer did
9118
+ // not match, a rule refused it), as for the first PUT. 408 and 499 are a proxy's words,
9119
+ // not realtime's, and may follow a commit, so they stay ambiguous too.
9040
9120
  sending = true;
9041
- const commitResponse = await makeApiRequest('PUT', 'items/signed', { intentId: curTx.poofnetIntentId, signedTransaction }, options === null || options === void 0 ? void 0 : options._overrides);
9042
- if (commitResponse.status !== 200) {
9043
- throw new Error(`Poofnet signed write was not committed (status ${commitResponse.status})`);
9121
+ let commitResponse;
9122
+ try {
9123
+ 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);
9124
+ if (commitResponse.status !== 200) {
9125
+ throw Object.assign(new Error(`Poofnet signed write was not committed (status ${commitResponse.status})`), { statusCode: commitResponse.status });
9126
+ }
9127
+ }
9128
+ catch (error) {
9129
+ const status = Number(error === null || error === void 0 ? void 0 : error.statusCode);
9130
+ throw status >= 400 && status < 500 && status !== 408 && status !== 499 ? markWriteNotApplied(error, 'broadcast_rejected') : error;
9044
9131
  }
9045
9132
  return directCommitResult(commitResponse, documents);
9046
9133
  }
@@ -9050,7 +9137,7 @@ async function setMany(many, options) {
9050
9137
  'Nothing was signed or submitted.');
9051
9138
  }
9052
9139
  const solanaChain = workerChain;
9053
- const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await import('./solana-operations-Cshh2EfH.mjs');
9140
+ const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await import('./solana-operations-BLbRO1rE.mjs');
9054
9141
  // OA-0001: derive the expected intent from OUR OWN request (the one
9055
9142
  // ground truth a tampered builder response cannot change) so the
9056
9143
  // validator can reject any transaction whose operation semantics
@@ -9089,7 +9176,9 @@ async function setMany(many, options) {
9089
9176
  }
9090
9177
  // Confirmed: the receipt is already complete, so the mirror read is a
9091
9178
  // best-effort enrichment reported in `mirrorSync`, never a rejection.
9092
- const mirrorSync = await attemptMirrorSync(requestedPaths, options);
9179
+ const mirrorSync = grantLane
9180
+ ? { observed: false, reason: 'not attempted: a venue grant authorizes only the write' }
9181
+ : await attemptMirrorSync(requestedPaths, options);
9093
9182
  return {
9094
9183
  status: 'confirmed', appId, requestedDocuments, chain: solanaChain,
9095
9184
  transactionId: solanaOutcome.transactionSignature,
@@ -10112,6 +10201,84 @@ function createAppTokenAuthHeaders(options) {
10112
10201
  };
10113
10202
  }
10114
10203
 
10204
+ /**
10205
+ * The venue grant write lane: create ONE document in another app (an OpenApps venue) as the
10206
+ * wallet signed in here, authorized by a single-use grant instead of a venue session
10207
+ * (VENUE-GRANT-DESIGN.md). The grant only lets the venue build that one transaction for that
10208
+ * wallet; the wallet's own signature, checked by the venue's own rule, is the authority.
10209
+ *
10210
+ * The grant itself is minted by Bounded Auth at `POST {humanAuthApiUrl}/app-token/venue-grant`
10211
+ * (contract: packages/cdk/cloudflare/shared/venue-grant.ts, which this package cannot import
10212
+ * because the SDK packages publish separately). This module only spends one.
10213
+ *
10214
+ * It reuses `setMany`'s request and 202 handling through `_overrides`: the `_config` snapshot is
10215
+ * this app's own config re-pointed at the VENUE app id, so the pre-sign validator (OA-0001) binds
10216
+ * the venue app id and the full expected intent, and the signer stays this app's own AuthProvider
10217
+ * (a Turnkey session or a Wallet Standard wallet). Nothing here is reachable from `createClient()`,
10218
+ * which stays read-only.
10219
+ *
10220
+ * The chain a write runs on is the VENUE's, stated by its 202, never the app's: the app's own
10221
+ * `chain` can only agree with it or be refused, and the transaction is broadcast through the app's
10222
+ * `rpcUrl` only when the app configured that same chain, otherwise through `resolveRpcUrl(chain)`.
10223
+ */
10224
+ function laneError(code, message) {
10225
+ return Object.assign(new Error(message), { code });
10226
+ }
10227
+ const isSolanaLane = (chain) => chain === 'solana_devnet' || chain === 'solana_mainnet';
10228
+ /**
10229
+ * The RPC a venue write on Solana lane `chain` is broadcast through, and a saved receipt of it is
10230
+ * reconciled through. The app's own `rpcUrl` counts only when the app also configured this chain
10231
+ * (an `rpcUrl` alone does not say which chain it speaks); otherwise `resolveRpcUrl(chain)`. Null
10232
+ * when neither yields one.
10233
+ *
10234
+ * @internal Shared by the lane and by the client SDK's reconciliation of a saved receipt.
10235
+ */
10236
+ function resolveVenueRpcUrl(config, chain, resolveRpcUrl) {
10237
+ var _a, _b;
10238
+ const own = requiredSolanaChain(config) === chain ? (_a = config.rpcUrl) === null || _a === void 0 ? void 0 : _a.trim() : '';
10239
+ return own || ((_b = resolveRpcUrl === null || resolveRpcUrl === void 0 ? void 0 : resolveRpcUrl(chain)) === null || _b === void 0 ? void 0 : _b.trim()) || null;
10240
+ }
10241
+ /**
10242
+ * Refuse, before the wallet is asked to sign, a 202 whose execution lane this app is not
10243
+ * configured for: a mainnet app must never sign a devnet or poofnet approval (a poofnet "funding"
10244
+ * moves no real USDC). Then point the Solana lane's broadcast at the RPC for the venue's chain.
10245
+ * Poofnet broadcasts nothing, so it needs no RPC.
10246
+ */
10247
+ function assertExecutionLane(config, chain, options) {
10248
+ const required = requiredSolanaChain(config);
10249
+ if (required && chain !== required) {
10250
+ throw laneError('chain_mismatch', `The venue answered on "${chain}" but this app is configured for "${config.chain}". Nothing was signed.`);
10251
+ }
10252
+ if (!isSolanaLane(chain))
10253
+ return;
10254
+ const rpcUrl = resolveVenueRpcUrl(config, chain, options.resolveRpcUrl);
10255
+ if (rpcUrl)
10256
+ config.rpcUrl = rpcUrl;
10257
+ else if (!options._solanaRpc) {
10258
+ throw laneError('chain_not_configured', `The venue runs on ${chain}, but no RPC could be resolved to broadcast through. ` +
10259
+ `Pass chain: '${chain}' and an rpcUrl to init(). Nothing was signed.`);
10260
+ }
10261
+ }
10262
+ /**
10263
+ * Write `document` at `path` in the venue `venueAppId` with a single-use grant. Resolves to the
10264
+ * same discriminated {@link SetResult} as `set`, and throws exactly as `set` does, so the same
10265
+ * `isWriteNotApplied` reading applies.
10266
+ *
10267
+ * @internal Used by `fundVenueEscrow` and `disputeVenueEscrow` in @bounded-sh/client. Not a stable API.
10268
+ */
10269
+ async function setWithVenueGrant(path, document, options) {
10270
+ // A private copy of this app's config for this one write: the lane fills in its RPC below.
10271
+ const config = Object.assign(Object.assign({}, (await getConfig())), { appId: options.venueAppId });
10272
+ const headers = (grant) => ({ Authorization: `Bearer ${grant}` });
10273
+ return setMany([{ path, document }], {
10274
+ onBroadcast: options.onBroadcast,
10275
+ _overrides: Object.assign({ _config: config, _getAuthHeaders: async () => headers(options.grant), _grantLane: {
10276
+ assertLane: (chain) => assertExecutionLane(config, chain, options),
10277
+ commitHeaders: async () => headers(await options.commitGrant()),
10278
+ } }, (options._solanaRpc ? { _solanaRpc: options._solanaRpc } : {})),
10279
+ });
10280
+ }
10281
+
10115
10282
  /**
10116
10283
  * Live named-query subscriptions (subscribeQuery) - HTTP client poller (v1).
10117
10284
  *
@@ -10814,7 +10981,7 @@ function toMillis(seconds) {
10814
10981
 
10815
10982
  /** SIWS message creation is async already, so its onchain module can be lazy. */
10816
10983
  async function genSolanaMessage(address, nonce) {
10817
- const implementation = await import('./solana-message-BBMxFFjM.mjs');
10984
+ const implementation = await import('./solana-message-BNGEBst_.mjs');
10818
10985
  return implementation.genSolanaMessage(address, nonce);
10819
10986
  }
10820
10987
 
@@ -11363,5 +11530,5 @@ function defineLiveModule(mod) {
11363
11530
  return mod;
11364
11531
  }
11365
11532
 
11366
- export { increment as $, isWriteNotApplied as A, writeNotAppliedPhase as B, subscribe as C, closeAllSubscriptions as D, clearCache as E, getCachedData as F, reconnectWithNewAuth as G, createClient as H, InsufficientBalanceError as I, exchangeAppToken as J, clearAppTokenCache as K, createAppTokenAuthHeaders as L, AppTokenExchangeError as M, subscribeQuery as N, closeAllQuerySubscriptions as O, canonicalizeArgs as P, canonicalizeResult as Q, DEFAULT_QUERY_POLL_INTERVAL_MS as R, ScopedClientReadOnlyError as S, MIN_QUERY_POLL_INTERVAL_MS as T, BoundedDeclineError as U, deriveDecline as V, dispatchDeclineEvent as W, DECLINE_EVENT_NAME as X, dispatchRateLimitEvent as Y, RATE_LIMIT_EVENT_NAME as Z, hasActiveConnection as _, getWebhookKeysUrl as a, serverTimestamp as a0, now as a1, toSeconds as a2, toMillis as a3, getIdToken as a4, deriveUserIdentityFromIdToken as a5, getSessionGeneration as a6, WebSessionManager as a7, ReactNativeSessionManager as a8, getActiveSessionManager as a9, live as aA, intent as aB, status as aC, subscribeView as aD, LiveIntentError as aE, withEffects as aF, isEffectResult as aG, defineLiveModule as aH, EFFECT_INTENT_ADDRESS as aI, onSessionCleared as aa, SessionEnvironmentError as ab, UNSUPPORTED_ENVIRONMENT_CODE as ac, AUTH_CHANGED_CODE as ad, AUTH_EXPIRED_CODE as ae, isAuthExpiredError as af, getFreshAuthToken as ag, ServerSessionManager as ah, createSessionWithSignature as ai, refreshSession as aj, revokeSession as ak, signSessionCreateMessage as al, genAuthNonce as am, genSolanaMessage as an, createSolanaSignInRequest as ao, createSolanaSignInMessageText as ap, genSolanaSignInRequest as aq, genSolanaSignInMessage as ar, browserSignInDomain as as, SOLANA_SIGN_IN_STATEMENT as at, genSiweMessage as au, normalizeEvmAddress as av, toLowerEvmAddress as aw, functions as ax, invoke as ay, FunctionInvokeError as az, bufferExports as b, getConfiguredAppId as c, get as d, getMany as e, docId as f, getConfig as g, setMany as h, init as i, setFile as j, getFiles as k, search as l, markWriteNotApplied as m, runQueryMany as n, runExpression as o, runExpressionMany as p, queryAggregate as q, runQuery as r, set as s, signMessage as t, signTransaction as u, signAndSubmitTransaction as v, count as w, aggregate as x, reconcileSetResult as y, waitForSetResult as z };
11367
- //# sourceMappingURL=index-CkgEqMkJ.mjs.map
11533
+ export { RATE_LIMIT_EVENT_NAME as $, isWriteNotApplied as A, writeNotAppliedPhase as B, subscribe as C, closeAllSubscriptions as D, clearCache as E, getCachedData as F, reconnectWithNewAuth as G, createClient as H, InsufficientBalanceError as I, exchangeAppToken as J, clearAppTokenCache as K, createAppTokenAuthHeaders as L, AppTokenExchangeError as M, setWithVenueGrant as N, resolveVenueRpcUrl as O, subscribeQuery as P, closeAllQuerySubscriptions as Q, canonicalizeArgs as R, ScopedClientReadOnlyError as S, canonicalizeResult as T, DEFAULT_QUERY_POLL_INTERVAL_MS as U, MIN_QUERY_POLL_INTERVAL_MS as V, BoundedDeclineError as W, deriveDecline as X, dispatchDeclineEvent as Y, DECLINE_EVENT_NAME as Z, dispatchRateLimitEvent as _, getWebhookKeysUrl as a, hasActiveConnection as a0, increment as a1, serverTimestamp as a2, now as a3, toSeconds as a4, toMillis as a5, getIdToken as a6, deriveUserIdentityFromIdToken as a7, getSessionGeneration as a8, WebSessionManager as a9, invoke as aA, FunctionInvokeError as aB, live as aC, intent as aD, status as aE, subscribeView as aF, LiveIntentError as aG, withEffects as aH, isEffectResult as aI, defineLiveModule as aJ, EFFECT_INTENT_ADDRESS as aK, ReactNativeSessionManager as aa, getActiveSessionManager as ab, onSessionCleared as ac, SessionEnvironmentError as ad, UNSUPPORTED_ENVIRONMENT_CODE as ae, AUTH_CHANGED_CODE as af, AUTH_EXPIRED_CODE as ag, isAuthExpiredError as ah, getFreshAuthToken as ai, ServerSessionManager as aj, createSessionWithSignature as ak, refreshSession as al, revokeSession as am, signSessionCreateMessage as an, genAuthNonce as ao, genSolanaMessage as ap, createSolanaSignInRequest as aq, createSolanaSignInMessageText as ar, genSolanaSignInRequest as as, genSolanaSignInMessage as at, browserSignInDomain as au, SOLANA_SIGN_IN_STATEMENT as av, genSiweMessage as aw, normalizeEvmAddress as ax, toLowerEvmAddress as ay, functions as az, bufferExports as b, getConfiguredAppId as c, get as d, getMany as e, docId as f, getConfig as g, setMany as h, init as i, setFile as j, getFiles as k, search as l, markWriteNotApplied as m, runQueryMany as n, runExpression as o, runExpressionMany as p, queryAggregate as q, runQuery as r, set as s, signMessage as t, signTransaction as u, signAndSubmitTransaction as v, count as w, aggregate as x, reconcileSetResult as y, waitForSetResult as z };
11534
+ //# sourceMappingURL=index-BilGFb3V.mjs.map