@capxul/sdk 4.2.0 → 4.20.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,14 +1,8 @@
1
- import { $ as stampTelemetryEnvelope, A as safeExceptionLabel, B as resolveFailureMode, C as observeFailedResult, D as normalizeExceptionErrorKind, E as SDK_VERSION$1, F as causeChain, G as sanitizeObservationContext, H as readInvocationObservation, I as injectedWalletSigner, J as CAPXUL_FUNCTIONS, K as PAYMENT_DIRECTIONS, L as isSignerNotReadySignal, M as fromWei, N as isSettingUpLifecycle, O as normalizeExceptionOperation, P as formatTraceparent, Q as redactTelemetryProps, R as openfortSignerNotReadyCode, S as observationContextProps, T as EXCEPTION_MESSAGE, U as OBSERVATION_CONTEXT_HEADER, V as copyInvocationObservation, W as encodeObservationContextHeader, X as EngineeringTelemetryBootstrapPolicy, Y as BootstrapEnvelope, _ as fingerprintPaymentIntent, a as AccountReadPortTag, at as isRestoring, b as failureDetail, c as retryIdempotentRead, d as IdentityPortTag, et as CAPXUL_OPERATIONS, f as identityErrorFromCapxul, g as bootstrapErrorFromCapxul, h as BootstrapPortTag, i as smartAccountErrorFromCapxul, it as isClaimed, j as version, k as projectSdkException, l as PRODUCT_INVOCATION, m as convexCallErrorFromCapxul, n as detectAuthCacheAdapter, nt as normalizeCapxulOperation, o as accountReadErrorFromCapxul, p as ConvexCallPortTag, q as PAYMENT_STATUSES, r as SmartAccountPortTag, rt as destination, s as TelemetryPortTag, t as assembleCapxulClient, tt as isCapxulOperation, u as wireChainId, v as toWei, w as postHogFailureObservation, x as failureEvidenceProps, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as signerFailure } from "./create-capxul-client-B6PUjdld.mjs";
2
- import { $ as CapxulError, A as toAuthUserId, B as toJwtToken, D as toAddress, E as toAccountId, F as toDurationMs, H as toOrgId, I as toEmail, J as toSessionToken, K as toPublishableKey, L as toEpochMs, M as toChainId, N as toCountryCode, O as toAllowedOrigin, P as toCurrencyCode, Q as CAPXUL_ERROR_CODES, R as toEpochSeconds, T as currencySymbolFor, U as toPartyId, V as toKycTier, X as HANDLE_RE, Y as toTesterKind, Z as decodeConvexError, d as AuthClientError, f as AuthClientPortTag, g as BASE_SEPOLIA_CHAIN_ID, gt as redactUrlSecrets, h as normalizeBindingEmail, ht as redactSecrets, it as FAILURE_MODES, l as ClockPortTag, m as CAPXUL_PAYMENTS_V2_ADDRESS, mt as isCredentialField, nt as isCapxulError, q as toRoleKey, rt as CHAIN_UPSTREAMS, s as AuthCachePortTag, tt as Errors, u as authClientPortFromPromiseAdapter, x as EVM_ADDRESS_RE, z as toHandle } from "./clock-DIfTX44d.mjs";
3
- import { n as SystemClockLayer } from "./OAuthBearerAuthClient-ByPzJZr8.mjs";
4
- import { Cause, Context, Data, Duration, Effect, Exit, FiberSet, Layer, Result, Schedule, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
5
- import { keccak256, recoverAddress, stringToHex } from "viem";
6
- import { getFunctionName, makeFunctionReference } from "convex/server";
1
+ import { A as signerFailure, B as isRestoring, C as normalizeExceptionErrorKind, E as safeExceptionLabel, F as CAPXUL_OPERATIONS, I as isCapxulOperation, L as normalizeCapxulOperation, M as PAYMENT_DIRECTIONS, N as PAYMENT_STATUSES, O as isSettingUpLifecycle, R as destination, S as SDK_VERSION, T as projectSdkException, _ as fingerprintPaymentIntent, b as failureDetail, d as embeddedSigner, f as openfortEmbeddedSigner, g as devPrivateKeySigner, h as deriveDevPrivateKey, j as resolveFailureMode, k as injectedWalletSigner, m as openfortEmbeddedWalletPort, p as openfortEmbeddedSignerFromWallet, r as postHogObservability, t as createCapxulClient$1, w as normalizeExceptionOperation, x as EXCEPTION_MESSAGE, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as isClaimed } from "./production-BmsFhDUr.mjs";
2
+ import { A as currencySymbolFor, B as toCurrencyCode, G as toHandle, M as toAddress, T as EVM_ADDRESS_RE, Y as toPartyId, at as HANDLE_RE, b as configuredMoneyAssetById, ct as CapxulError, dt as isCapxulError, ft as CHAIN_UPSTREAMS, g as CAPXUL_PAYMENTS_V2_ADDRESS, k as assetIdFor, pt as FAILURE_MODES, st as CAPXUL_ERROR_CODES, ut as Errors, z as toCountryCode } from "./OAuthBearerAuthClient-DDD0JlaI.mjs";
3
+ import { formatUnits } from "viem";
4
+ import { Effect } from "effect";
7
5
  import { privateKeyToAccount } from "viem/accounts";
8
- import { FetchHttpClient, Headers, HttpClient } from "effect/unstable/http";
9
- import { OtlpExporter, OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
10
- import { ConvexClient } from "convex/browser";
11
- import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
12
6
  //#region src/domain/activity/phase.ts
13
7
  const PENDING = Object.freeze({
14
8
  terminal: false,
@@ -92,40 +86,6 @@ function inboxPhase(status) {
92
86
  }
93
87
  }
94
88
  //#endregion
95
- //#region src/domain/money/format-money.ts
96
- function formatMoney(money, options = {}) {
97
- if (!Number.isInteger(money.decimals) || money.decimals < 0) throw Errors.invalidInput("money.decimals", "must be a non-negative integer");
98
- const symbol = currencySymbolFor(toCurrencyCode(money.currency));
99
- const maximumFractionDigits = options.grammar === "code" ? 4 : 2;
100
- const rounded = roundDecimal(money.value, money.decimals, maximumFractionDigits);
101
- const grouped = rounded.integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
102
- const signed = `${rounded.negative ? "-" : ""}${grouped}`;
103
- if (options.grammar === "code") {
104
- if (!rounded.hadFraction) return `${signed} ${String(money.currency)}`;
105
- let displayedFraction = rounded.fraction;
106
- while (displayedFraction.length > 2 && displayedFraction.endsWith("0")) displayedFraction = displayedFraction.slice(0, -1);
107
- return `${signed}.${displayedFraction} ${String(money.currency)}`;
108
- }
109
- return `${symbol}${signed}.${rounded.fraction}`;
110
- }
111
- function roundDecimal(value, decimals, maximumFractionDigits) {
112
- const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(value);
113
- if (match === null) throw Errors.invalidInput("money.value", "must be a base-10 decimal string");
114
- const integer = match[2] ?? "0";
115
- const sourceFraction = match[3] ?? "";
116
- if (sourceFraction.length > decimals) throw Errors.invalidInput("money.value", "fraction exceeds money.decimals");
117
- const scale = 10n ** BigInt(maximumFractionDigits);
118
- const keptFraction = sourceFraction.slice(0, maximumFractionDigits).padEnd(maximumFractionDigits, "0");
119
- let scaled = BigInt(integer) * scale + BigInt(keptFraction || "0");
120
- if ((sourceFraction[maximumFractionDigits] ?? "0") >= "5") scaled += 1n;
121
- return {
122
- negative: match[1] === "-",
123
- integer: (scaled / scale).toString(),
124
- fraction: (scaled % scale).toString().padStart(maximumFractionDigits, "0"),
125
- hadFraction: /[1-9]/.test(sourceFraction)
126
- };
127
- }
128
- //#endregion
129
89
  //#region src/domain/money/parse-money.ts
130
90
  /**
131
91
  * ADR-0023 R1: the reason is a closed code and the ONLY failure vocabulary —
@@ -138,13 +98,22 @@ function isMoneyParseError(value) {
138
98
  const AMOUNT_PATTERN = /^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?$/;
139
99
  const ZERO_PATTERN = /^0+(?:\.0+)?$/;
140
100
  function parseMoney(input, asset) {
141
- if (!Number.isInteger(asset.decimals) || asset.decimals < 0) throw Errors.invalidInput("asset.decimals", "must be a non-negative integer");
101
+ const parsed = parseDecimalInput(input, asset.decimals, 24);
102
+ return "kind" in parsed ? parsed : {
103
+ currency: asset.currency,
104
+ value: parsed.value,
105
+ decimals: asset.decimals
106
+ };
107
+ }
108
+ /** Shared input grammar. Callers retain their own amount size limits. */
109
+ function parseDecimalInput(input, decimals, maximumLength) {
110
+ if (!Number.isInteger(decimals) || decimals < 0) throw Errors.invalidInput("asset.decimals", "must be a non-negative integer");
142
111
  const value = input.trim();
143
112
  if (value.length === 0) return {
144
113
  kind: "error",
145
114
  reason: "required"
146
115
  };
147
- if (value.length > 24) return {
116
+ if (value.length > maximumLength) return {
148
117
  kind: "error",
149
118
  reason: "too-long"
150
119
  };
@@ -160,7 +129,7 @@ function parseMoney(input, asset) {
160
129
  };
161
130
  const normalized = unsigned.replaceAll(",", "");
162
131
  const fraction = normalized.split(".")[1];
163
- if (fraction !== void 0 && fraction.length > asset.decimals) return {
132
+ if (fraction !== void 0 && fraction.length > decimals) return {
164
133
  kind: "error",
165
134
  reason: "too-many-decimals"
166
135
  };
@@ -168,10 +137,133 @@ function parseMoney(input, asset) {
168
137
  kind: "error",
169
138
  reason: "non-positive"
170
139
  };
140
+ return { value: normalized };
141
+ }
142
+ //#endregion
143
+ //#region src/domain/money/asset-amount.ts
144
+ /** Exact scaling. This rejects excess precision instead of rounding it. */
145
+ function toRaw(amount, asset) {
146
+ validateMetadata(asset);
147
+ if (amount.asset !== asset.assetId) throw Errors.invalidInput("amount.asset", "must match the selected asset on its chain");
148
+ return decimalToRaw(amount.value, asset.decimals);
149
+ }
150
+ function parseAssetAmount(input, asset) {
151
+ validateMetadata(asset);
152
+ const parsed = parseDecimalInput(input, asset.decimals, 100);
153
+ return "kind" in parsed ? parsed : {
154
+ asset: asset.assetId,
155
+ value: parsed.value.replace(/^0+(?=\d)/, "")
156
+ };
157
+ }
158
+ /** Display the full quantity. Only fiat formatting rounds for display. */
159
+ function formatAssetAmount(amount) {
160
+ const asset = requireAsset(amount.asset);
161
+ const [integer = "0", fraction] = formatUnits(toRaw(amount, asset), asset.decimals).split(".");
162
+ return `${integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}${fraction === void 0 ? "" : `.${fraction}`} ${asset.symbol}`;
163
+ }
164
+ function assetSymbolFor(asset) {
165
+ return requireAsset(asset).symbol;
166
+ }
167
+ function requireAsset(id) {
168
+ const asset = configuredMoneyAssetById(id);
169
+ if (asset === null) throw Errors.invalidInput("amount.asset", "is not a registered asset");
170
+ return asset;
171
+ }
172
+ function decimalToRaw(value, decimals) {
173
+ validatePrecision(decimals);
174
+ if (!/^\d+(?:\.\d+)?$/.test(value) || value.trim() !== value || value.length > 512) throw Errors.invalidInput("amount.value", "must be a non-negative decimal string of at most 512 characters");
175
+ const [integer = "0", fraction = ""] = value.split(".");
176
+ if (fraction.length > decimals) throw Errors.invalidInput("amount.value", "fraction exceeds asset precision");
177
+ return BigInt(integer) * 10n ** BigInt(decimals) + BigInt(fraction.padEnd(decimals, "0") || "0");
178
+ }
179
+ function validatePrecision(decimals) {
180
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) throw Errors.invalidInput("decimals", "must be an integer from 0 to 255");
181
+ }
182
+ function validateMetadata(asset) {
183
+ validatePrecision(asset.decimals);
184
+ if (asset.assetId !== assetIdFor(asset.chainId, asset.tokenAddress)) throw Errors.invalidInput("amount.asset", "metadata must match its chain and token");
185
+ }
186
+ //#endregion
187
+ //#region src/domain/money/valuation.ts
188
+ const PEG_RATES = (source, into) => (typeof source === "string" ? source : source.peg) === into ? {
189
+ value: "1",
190
+ decimals: 0
191
+ } : null;
192
+ /** Sum exact fixed-point values. Missing rates never contribute zero. */
193
+ function valueIn(amounts, displayCurrency, rates = PEG_RATES) {
194
+ const currency = toCurrencyCode(displayCurrency);
195
+ const counted = [];
196
+ const unrated = [];
197
+ let sum = 0n;
198
+ let scale = 0;
199
+ for (const amount of amounts) {
200
+ let source;
201
+ let raw;
202
+ let decimals;
203
+ if ("asset" in amount) {
204
+ source = requireAsset(amount.asset);
205
+ raw = toRaw(amount, source);
206
+ decimals = source.decimals;
207
+ } else {
208
+ source = toCurrencyCode(amount.currency);
209
+ raw = decimalToRaw(amount.value, amount.decimals);
210
+ decimals = amount.decimals;
211
+ }
212
+ const rate = rates(source, currency);
213
+ if (rate === null) {
214
+ unrated.push(amount);
215
+ continue;
216
+ }
217
+ validatePrecision(rate.decimals);
218
+ if (!/^\d+$/.test(rate.value) || rate.value.trim() !== rate.value || rate.value.length > 256 || BigInt(rate.value) <= 0n) throw Errors.invalidInput("rate.value", "must be a positive fixed-point integer");
219
+ const termScale = decimals + rate.decimals;
220
+ const nextScale = Math.max(scale, termScale);
221
+ sum = sum * 10n ** BigInt(nextScale - scale) + raw * BigInt(rate.value) * 10n ** BigInt(nextScale - termScale);
222
+ scale = nextScale;
223
+ counted.push(amount);
224
+ }
225
+ return {
226
+ total: counted.length === 0 ? null : {
227
+ currency,
228
+ value: formatUnits(sum, scale),
229
+ decimals: scale
230
+ },
231
+ counted,
232
+ unrated
233
+ };
234
+ }
235
+ //#endregion
236
+ //#region src/domain/money/format-money.ts
237
+ function formatMoney(money, options = {}) {
238
+ if (!Number.isInteger(money.decimals) || money.decimals < 0) throw Errors.invalidInput("money.decimals", "must be a non-negative integer");
239
+ const symbol = currencySymbolFor(toCurrencyCode(money.currency));
240
+ const maximumFractionDigits = options.grammar === "code" ? 4 : 2;
241
+ const rounded = roundDecimal(money.value, money.decimals, maximumFractionDigits);
242
+ const grouped = rounded.integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
243
+ const signed = `${rounded.negative ? "-" : ""}${grouped}`;
244
+ if (options.grammar === "code") {
245
+ if (!rounded.hadFraction) return `${signed} ${String(money.currency)}`;
246
+ let displayedFraction = rounded.fraction;
247
+ while (displayedFraction.length > 2 && displayedFraction.endsWith("0")) displayedFraction = displayedFraction.slice(0, -1);
248
+ return `${signed}.${displayedFraction} ${String(money.currency)}`;
249
+ }
250
+ return `${symbol}${signed}.${rounded.fraction}`;
251
+ }
252
+ function roundDecimal(value, decimals, maximumFractionDigits) {
253
+ const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(value);
254
+ if (match === null) throw Errors.invalidInput("money.value", "must be a base-10 decimal string");
255
+ const integer = match[2] ?? "0";
256
+ const sourceFraction = match[3] ?? "";
257
+ if (sourceFraction.length > decimals) throw Errors.invalidInput("money.value", "fraction exceeds money.decimals");
258
+ const scale = 10n ** BigInt(maximumFractionDigits);
259
+ const keptFraction = sourceFraction.slice(0, maximumFractionDigits).padEnd(maximumFractionDigits, "0");
260
+ let scaled = BigInt(integer) * scale + BigInt(keptFraction || "0");
261
+ if ((sourceFraction[maximumFractionDigits] ?? "0") >= "5") scaled += 1n;
171
262
  return {
172
- currency: asset.currency,
173
- value: normalized,
174
- decimals: asset.decimals
263
+ negative: match[1] === "-",
264
+ integer: (scaled / scale).toString(),
265
+ fraction: (scaled % scale).toString().padStart(maximumFractionDigits, "0"),
266
+ hadFraction: /[1-9]/.test(sourceFraction)
175
267
  };
176
268
  }
177
269
  //#endregion
@@ -245,3523 +337,6 @@ function firstAccount(value) {
245
337
  return typeof first === "string" ? first : null;
246
338
  }
247
339
  //#endregion
248
- //#region src/dev-signer.ts
249
- const SESSION_KEY = "capxul.session";
250
- /**
251
- * Chains a `local-private-key` signer may sign on. Base Sepolia only, and this
252
- * list does not grow without an ADR: the keys are deterministic throwaways
253
- * derived from a shared seed (see `deriveDevPrivateKey`), so anyone holding
254
- * the seed holds every account. A dev key on a value-bearing chain is a
255
- * custody incident, not a config mistake.
256
- */
257
- const DEV_KEY_ALLOWED_CHAIN_IDS = [BASE_SEPOLIA_CHAIN_ID];
258
- /**
259
- * Testnet fence for the dev-key signing lane (#1149, folds #1065; ADR-0018 P9
260
- * human/Openfort vs agent/dev-key split). Call it wherever a signer first
261
- * meets a resolved chain; it refuses before the signer can be used.
262
- *
263
- * Only `local-private-key` signers are fenced — Openfort-embedded and injected
264
- * wallets carry their own custody and are the sanctioned human paths.
265
- */
266
- function assertDevKeySignerIsTestnetOnly(signer, chainId) {
267
- if (signer?.source !== "local-private-key") return {
268
- ok: true,
269
- value: void 0
270
- };
271
- if (DEV_KEY_ALLOWED_CHAIN_IDS.includes(chainId)) return {
272
- ok: true,
273
- value: void 0
274
- };
275
- return {
276
- ok: false,
277
- error: Errors.invalidInput("signer", `dev-key signer is testnet-only: chain ${chainId} is not allowed (expected ${DEV_KEY_ALLOWED_CHAIN_IDS.join(", ")})`)
278
- };
279
- }
280
- /** Deterministic dev private key for an email under a seed. Exported for probes. */
281
- function deriveDevPrivateKey(seed, email) {
282
- return keccak256(stringToHex(seed + normalizeBindingEmail(email)));
283
- }
284
- function readSessionEmail(storage) {
285
- const resolved = storage ?? globalThis.window?.localStorage;
286
- if (resolved === void 0) throw new Error("devPrivateKeySigner: no browser localStorage available and no explicit email supplied");
287
- const raw = resolved.getItem(SESSION_KEY);
288
- if (raw === null) throw new Error("devPrivateKeySigner: no cached session yet — sign in before the account lane uses the signer");
289
- let email;
290
- try {
291
- email = JSON.parse(raw).email;
292
- } catch {
293
- throw new Error("devPrivateKeySigner: cached session is not valid JSON");
294
- }
295
- if (typeof email !== "string" || email.length === 0) throw new Error("devPrivateKeySigner: cached session has no email");
296
- return email;
297
- }
298
- /**
299
- * Browser-safe dev signer. Lazy: the email (and so the key) is resolved at
300
- * each `getAddress()` / `signUserOpHash()` from the cached session, so the
301
- * same signer instance follows whichever user is signed in.
302
- */
303
- function devPrivateKeySigner(input) {
304
- if (input.seed.trim().length === 0) throw new Error("devPrivateKeySigner: seed must be non-empty");
305
- const accounts = /* @__PURE__ */ new Map();
306
- const resolveAccount = () => {
307
- const email = normalizeBindingEmail(input.email ?? readSessionEmail(input.storage));
308
- const cached = accounts.get(email);
309
- if (cached !== void 0) return cached;
310
- const account = privateKeyToAccount(deriveDevPrivateKey(input.seed, email));
311
- accounts.set(email, account);
312
- return account;
313
- };
314
- return {
315
- source: "local-private-key",
316
- async getAddress() {
317
- return toAddress(resolveAccount().address);
318
- },
319
- async signUserOpHash(hash) {
320
- return resolveAccount().sign({ hash });
321
- }
322
- };
323
- }
324
- //#endregion
325
- //#region src/ports/embedded-wallet.ts
326
- function openfortEmbeddedWalletPort(input) {
327
- return {
328
- async getAddress() {
329
- if (input.ensureReady !== void 0) await input.ensureReady();
330
- return (await input.embeddedWallet.get()).address;
331
- },
332
- async signRawDigest(hash) {
333
- if (input.ensureReady !== void 0) await input.ensureReady();
334
- return await input.embeddedWallet.signMessage(hash, {
335
- hashMessage: false,
336
- arrayifyMessage: false
337
- });
338
- }
339
- };
340
- }
341
- //#endregion
342
- //#region src/openfort-embedded-signer.ts
343
- const EVM_ADDRESS_HEX = /^0x[0-9a-fA-F]{40}$/;
344
- const ECDSA_SIGNATURE_HEX = /^0x[0-9a-fA-F]{130}$/;
345
- const SAFE_OP_DIGEST_HEX = /^0x[0-9a-fA-F]{64}$/;
346
- /** Browser helper: wrap an initialized Openfort `embeddedWallet` API. */
347
- function openfortEmbeddedSignerFromWallet(input) {
348
- return openfortEmbeddedSigner({ wallet: openfortEmbeddedWalletPort({
349
- embeddedWallet: input.embeddedWallet,
350
- ...input.ensureWalletReady === void 0 ? {} : { ensureReady: input.ensureWalletReady }
351
- }) });
352
- }
353
- function openfortEmbeddedSigner(input) {
354
- let cachedAddress = null;
355
- let addressInFlight = null;
356
- let cacheEpoch = 0;
357
- const resetAddressCache = () => {
358
- cacheEpoch += 1;
359
- cachedAddress = null;
360
- addressInFlight = null;
361
- };
362
- const resolveAddress = async () => {
363
- if (cachedAddress !== null) return cachedAddress;
364
- if (addressInFlight !== null) return addressInFlight;
365
- const epoch = cacheEpoch;
366
- addressInFlight = (async () => {
367
- try {
368
- const raw = await input.wallet.getAddress();
369
- if (!EVM_ADDRESS_HEX.test(raw)) throw new Error("openfortEmbeddedSigner: embedded wallet returned invalid address format");
370
- const address = toAddress(raw);
371
- if (epoch === cacheEpoch) cachedAddress = address;
372
- return address;
373
- } finally {
374
- if (epoch === cacheEpoch) addressInFlight = null;
375
- }
376
- })();
377
- return addressInFlight;
378
- };
379
- return {
380
- source: "openfort-embedded",
381
- getAddress: resolveAddress,
382
- resetAddressCache,
383
- async signUserOpHash(hash) {
384
- if (!SAFE_OP_DIGEST_HEX.test(hash)) throw new Error("openfortEmbeddedSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
385
- const address = await resolveAddress();
386
- let signature;
387
- try {
388
- signature = await input.wallet.signRawDigest(hash);
389
- } catch (cause) {
390
- const detail = cause instanceof Error ? cause.message : String(cause);
391
- throw new Error(`openfortEmbeddedSigner: raw digest signing failed; ensure the embedded wallet is configured (${detail})`, { cause });
392
- }
393
- if (!ECDSA_SIGNATURE_HEX.test(signature)) throw new Error("openfortEmbeddedSigner: embedded wallet returned invalid signature format");
394
- if ((await recoverRawDigestSigner({
395
- hash,
396
- signature
397
- })).toLowerCase() !== address.toLowerCase()) throw new Error("openfortEmbeddedSigner: signature did not recover the embedded wallet address for the raw SafeOp digest");
398
- return signature;
399
- }
400
- };
401
- }
402
- /**
403
- * Canonical name for the embedded-wallet `CapxulSigner` constructor
404
- * (backend-orchestrated-deploy.md). The embedded-wallet (passkey / Openfort)
405
- * member of the named constructor trio `localPrivateKeySigner` /
406
- * `injectedWalletSigner` / `embeddedSigner`. Takes the provider-agnostic
407
- * `OpenfortEmbeddedWalletPort` (getAddress + signRawDigest); the Openfort-API
408
- * convenience wrapper is `openfortEmbeddedSignerFromWallet`.
409
- */
410
- const embeddedSigner = openfortEmbeddedSigner;
411
- async function recoverRawDigestSigner(input) {
412
- try {
413
- return toAddress(await recoverAddress(input));
414
- } catch (cause) {
415
- const detail = cause instanceof Error ? cause.message : String(cause);
416
- throw new Error(`openfortEmbeddedSigner: could not verify raw SafeOp digest signature (${detail})`, { cause });
417
- }
418
- }
419
- //#endregion
420
- //#region ../observability/src/engineering.ts
421
- const ENGINEERING_CAPXUL_ENVS = [
422
- "development",
423
- "staging",
424
- "production",
425
- "local"
426
- ];
427
- const traceHeaderFilter = (_name) => true;
428
- const credentialHeaderPattern = Object.assign(/./u, { test: (name) => isCredentialField(name, "header") });
429
- const postHogOtlpEndpoints = (host) => {
430
- const base = host.replace(/\/+$/, "");
431
- return {
432
- logs: `${base}/i/v1/logs`,
433
- traces: `${base}/i/v1/traces`
434
- };
435
- };
436
- const ENGINEERING_CAPXUL_ENV_SET = new Set(ENGINEERING_CAPXUL_ENVS);
437
- const ENGINEERING_PRODUCERS = /* @__PURE__ */ new Set(["browser", "server"]);
438
- const PUBLIC_POSTHOG_AUTHORIZATION = /^Bearer phc_[A-Za-z0-9_-]{1,191}$/u;
439
- const SAFE_RESOURCE_VALUE = /^[A-Za-z0-9][A-Za-z0-9._+@/-]{0,127}$/u;
440
- const validateEngineeringTelemetryConfig = (config) => {
441
- let url;
442
- try {
443
- url = new URL(config.host);
444
- } catch {
445
- throw new TypeError("engineering telemetry host must be an absolute HTTPS URL");
446
- }
447
- if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0 || !(url.hostname === "posthog.com" || url.hostname.endsWith(".posthog.com"))) throw new TypeError("engineering telemetry host must be a credential-free HTTPS origin");
448
- if (!ENGINEERING_CAPXUL_ENV_SET.has(config.capxulEnv)) throw new TypeError("engineering telemetry capxulEnv is not canonical");
449
- if (!ENGINEERING_PRODUCERS.has(config.producer)) throw new TypeError("engineering telemetry producer is not canonical");
450
- if (!SAFE_RESOURCE_VALUE.test(config.sdkVersion)) throw new TypeError("engineering telemetry sdkVersion must be a bounded safe value");
451
- if (config.serviceName !== void 0 && !SAFE_RESOURCE_VALUE.test(config.serviceName)) throw new TypeError("engineering telemetry serviceName must be a bounded safe value");
452
- const headerEntries = Object.entries(config.headers);
453
- if (headerEntries.length !== 1 || headerEntries[0]?.[0].toLowerCase() !== "authorization" || !PUBLIC_POSTHOG_AUTHORIZATION.test(headerEntries[0]?.[1] ?? "")) throw new TypeError("engineering telemetry headers must contain exactly one public PostHog authorization token");
454
- return {
455
- ...config,
456
- host: url.origin,
457
- headers: Object.freeze({ authorization: headerEntries[0][1] })
458
- };
459
- };
460
- const failureText = (failure, key, limit, source) => {
461
- if (failure === null || typeof failure !== "object") return void 0;
462
- try {
463
- let owner = failure;
464
- let descriptor;
465
- for (let depth = 0; owner !== null && depth < 4; depth += 1) {
466
- descriptor = Object.getOwnPropertyDescriptor(owner, key);
467
- if (descriptor !== void 0 || key !== "name") break;
468
- owner = Object.getPrototypeOf(owner);
469
- }
470
- const value = descriptor?.value;
471
- return typeof value === "string" || key === "code" && typeof value === "number" ? redactSecrets(String(value), source).slice(0, limit) : void 0;
472
- } catch {
473
- return;
474
- }
475
- };
476
- const exportFailure = (failure, source) => {
477
- const message = failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
478
- const code = failureText(failure, "code", 128);
479
- const error = new Error((code === void 0 ? message : `${message} [code=${code}]`).slice(0, 2048));
480
- error.name = failureText(failure, "_tag", 128) ?? failureText(failure, "name", 128) ?? "Error";
481
- error.stack = failureText(failure, "stack", 8192, source) ?? `${error.name}: ${error.message}`;
482
- return error;
483
- };
484
- const exportFailureExit = (cause, source) => Exit.failCause(Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isInterruptReason(reason)).slice(0, 8).map((reason) => Cause.isFailReason(reason) ? Cause.makeFailReason(exportFailure(reason.error, source)) : Cause.makeDieReason(exportFailure(reason.defect, source)))));
485
- const BROWSER_KEEPALIVE_BYTES = 65536;
486
- let unfinishedBrowserExportBytes = 0;
487
- const hasBrowserDocument = () => typeof window !== "undefined" && typeof document !== "undefined";
488
- const browserExportClient = (client) => HttpClient.transform(client, (response, request) => Effect.acquireUseRelease(Effect.sync(() => {
489
- const bytes = "contentLength" in request.body ? request.body.contentLength : void 0;
490
- if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
491
- unfinishedBrowserExportBytes += bytes;
492
- return bytes;
493
- }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catch((error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error))))), (bytes) => Effect.sync(() => {
494
- if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
495
- })));
496
- const spanUpstream = (span) => {
497
- let current = span;
498
- while (current?._tag === "Span") {
499
- const upstream = current.attributes.get("chain.upstream");
500
- if (upstream === "alchemy" || upstream === "infura") return upstream;
501
- current = current.parent._tag === "Some" ? current.parent.value : void 0;
502
- }
503
- };
504
- /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
505
- const makeLeakSafeEngineeringTracer = (delegate) => Tracer.make({
506
- span(options) {
507
- const span = delegate.span(options);
508
- const wrapped = Object.create(span);
509
- Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
510
- const source = spanUpstream(span);
511
- const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
512
- if (header !== void 0 && isCredentialField(header, "header")) span.attribute(name, "[REDACTED]");
513
- else if (typeof value === "string" && name === "url.query") span.attribute(name, redactUrlSecrets(`?${value}`).slice(1, 2049));
514
- else if (typeof value === "string" && name === "url.path") {
515
- const fullUrl = span.attributes.get("url.full");
516
- const path = typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
517
- span.attribute(name, redactUrlSecrets(path, source).slice(0, 2048));
518
- } else if (typeof value === "string" && (name === "url.full" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
519
- else if (header !== void 0 && typeof value === "string") span.attribute(name, redactSecrets(value, source).slice(0, 2048));
520
- else span.attribute(name, value);
521
- } });
522
- Object.defineProperty(wrapped, "end", {
523
- configurable: false,
524
- enumerable: false,
525
- value: (endTime, exit) => {
526
- if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
527
- const first = exit.cause.reasons.find((reason) => !Cause.isInterruptReason(reason));
528
- const code = failureText(first === void 0 ? void 0 : Cause.isFailReason(first) ? first.error : first.defect, "code", 128);
529
- if (code !== void 0) span.attribute("error.code", code);
530
- span.end(endTime, exportFailureExit(exit.cause, spanUpstream(span)));
531
- } else span.end(endTime, exit);
532
- },
533
- writable: false
534
- });
535
- return wrapped;
536
- },
537
- ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
538
- });
539
- /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
540
- const makeEngineeringTelemetryLayer = (config) => {
541
- const validated = validateEngineeringTelemetryConfig(config);
542
- const endpoints = postHogOtlpEndpoints(validated.host);
543
- const resource = {
544
- serviceName: validated.serviceName ?? "capxul-sdk",
545
- serviceVersion: validated.sdkVersion,
546
- attributes: {
547
- capxul_env: validated.capxulEnv,
548
- producer: validated.producer,
549
- sdk_version: validated.sdkVersion
550
- }
551
- };
552
- const tracing = Layer.effect(Tracer.Tracer, OtlpTracer.make({
553
- url: endpoints.traces,
554
- headers: validated.headers,
555
- resource
556
- }).pipe(Effect.map(makeLeakSafeEngineeringTracer))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
557
- const logging = OtlpLogger.layer({
558
- url: endpoints.logs,
559
- headers: validated.headers,
560
- resource,
561
- mergeWithExisting: true
562
- });
563
- const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
564
- const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
565
- if (validated.producer !== "browser" || !hasBrowserDocument()) return;
566
- const browserWindow = window;
567
- const browserDocument = document;
568
- const flusher = yield* OtlpExporter.Flusher;
569
- const run = yield* FiberSet.makeRuntime();
570
- const flush = () => {
571
- run(flusher.flush);
572
- };
573
- const visibilityChanged = () => {
574
- if (browserDocument.visibilityState === "hidden") flush();
575
- };
576
- yield* Effect.acquireRelease(Effect.sync(() => {
577
- browserDocument.addEventListener("visibilitychange", visibilityChanged);
578
- browserWindow.addEventListener("pagehide", flush);
579
- }), () => Effect.sync(() => {
580
- browserDocument.removeEventListener("visibilitychange", visibilityChanged);
581
- browserWindow.removeEventListener("pagehide", flush);
582
- }));
583
- }));
584
- const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client)).pipe(Layer.provide(FetchHttpClient.layer));
585
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, headerPolicy)), Layer.provide(OtlpSerialization.layerJson), Layer.provide(transport));
586
- };
587
- //#endregion
588
- //#region src/adapters/auth-client/resolve-auth-url.ts
589
- /** Join bootstrap `authBaseUrl` with a BetterAuth route without duplicating `/api/auth`. */
590
- function resolveAuthClientUrl(authBaseUrl, path) {
591
- const base = authBaseUrl.replace(/\/$/, "");
592
- if (base.endsWith("/api/auth") && path.startsWith("/api/auth")) return `${base}${path.slice(9)}`;
593
- return `${base}${path}`;
594
- }
595
- //#endregion
596
- //#region src/internal/observation-http.ts
597
- /** Resolve one bounded pre-auth snapshot for an outbound SDK HTTP request. */
598
- function observationRequestHeaders(adapter, source) {
599
- if (adapter === void 0) return {};
600
- try {
601
- const invocation = readInvocationObservation(source);
602
- if (invocation !== void 0 && !invocation.active) return {};
603
- const encoded = encodeObservationContextHeader(invocation === void 0 ? adapter.resolveContext?.() : invocation.context);
604
- return encoded === void 0 ? {} : { [OBSERVATION_CONTEXT_HEADER]: encoded };
605
- } catch {
606
- return {};
607
- }
608
- }
609
- //#endregion
610
- //#region src/adapters/auth-client/BetterAuthBrowserAdapter.ts
611
- function withSignal$1(init, signal) {
612
- return signal === void 0 ? init : {
613
- ...init,
614
- signal
615
- };
616
- }
617
- const DEFAULT_JWT_LIFETIME_S$1 = 900;
618
- function decodeJwtExp$1(jwt) {
619
- const parts = jwt.split(".");
620
- if (parts.length < 2 || parts[1] === void 0) return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S$1;
621
- try {
622
- const raw = parts[1].replace(/\s+/g, "");
623
- const pad = "=".repeat((4 - raw.length % 4) % 4);
624
- const decoded = atob(raw.replace(/-/g, "+").replace(/_/g, "/") + pad);
625
- const payload = JSON.parse(decoded);
626
- if (typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > 0) return payload.exp;
627
- } catch {}
628
- return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S$1;
629
- }
630
- function authSessionFromBetterAuth$1(token, user) {
631
- return {
632
- authUserId: toAuthUserId(user.id),
633
- email: toEmail(user.email),
634
- token: toSessionToken(token),
635
- expiresAt: toEpochMs(Date.now() + 6048e5)
636
- };
637
- }
638
- async function safeJson$1(res) {
639
- const raw = await res.text();
640
- if (raw.length === 0 || raw === "null") return null;
641
- try {
642
- return JSON.parse(raw);
643
- } catch {
644
- return null;
645
- }
646
- }
647
- function mapBetterAuthError(operation, body) {
648
- if (typeof body === "object" && body !== null) {
649
- const errBody = body;
650
- const code = typeof errBody.code === "string" ? errBody.code : "";
651
- if (code === "OTP_EXPIRED") return Errors.otpExpired();
652
- if (code === "INVALID_OTP") return Errors.invalidInput("otp", errBody.message ?? "invalid OTP");
653
- if (code === "VALIDATION_ERROR" || code === "INVALID_EMAIL") return Errors.invalidInput("email", errBody.message ?? "invalid email");
654
- }
655
- return Errors.providerError("better-auth", operation, new Error(String(body)));
656
- }
657
- function isAbortError$1(err, signal) {
658
- return signal?.aborted === true || err instanceof Error && err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError";
659
- }
660
- function mapFetchError$1(operation, err, signal) {
661
- if (isAbortError$1(err, signal)) return Errors.cancelled({ operation });
662
- return Errors.networkError(operation, err instanceof Error ? err : new Error(String(err)));
663
- }
664
- var BetterAuthBrowserAdapter = class {
665
- authBaseUrl;
666
- fetchImpl;
667
- observation;
668
- constructor(deps) {
669
- this.authBaseUrl = deps.authBaseUrl.replace(/\/$/, "");
670
- this.observation = deps.observation;
671
- this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
672
- }
673
- url(path) {
674
- return resolveAuthClientUrl(this.authBaseUrl, path);
675
- }
676
- async canSendOtp(_input, options) {
677
- if (options?.signal?.aborted) return {
678
- ok: false,
679
- error: Errors.cancelled({ operation: "canSendOtp" })
680
- };
681
- return {
682
- ok: true,
683
- value: {
684
- allowed: true,
685
- cooldownMs: toDurationMs(0)
686
- }
687
- };
688
- }
689
- async sendOtp(input, options) {
690
- if (options?.signal?.aborted) return {
691
- ok: false,
692
- error: Errors.cancelled({ operation: "sendOtp" })
693
- };
694
- try {
695
- const res = await this.fetchImpl(this.url("/api/auth/email-otp/send-verification-otp"), withSignal$1({
696
- method: "POST",
697
- headers: {
698
- "Content-Type": "application/json",
699
- ...observationRequestHeaders(this.observation, input)
700
- },
701
- body: JSON.stringify({
702
- email: input.email,
703
- type: "sign-in"
704
- }),
705
- credentials: "include"
706
- }, options?.signal));
707
- if (options?.signal?.aborted) return {
708
- ok: false,
709
- error: Errors.cancelled({ operation: "sendOtp" })
710
- };
711
- if (res.ok) return {
712
- ok: true,
713
- value: void 0
714
- };
715
- if (res.status === 429) return {
716
- ok: false,
717
- error: Errors.rateLimited({ resource: "better-auth/sendOtp" })
718
- };
719
- const body = await safeJson$1(res);
720
- if (typeof body === "object" && body !== null) {
721
- const errBody = body;
722
- if (errBody.code === "INVALID_EMAIL" || errBody.code === "VALIDATION_ERROR") return {
723
- ok: false,
724
- error: Errors.invalidInput("email", errBody.message ?? "invalid email")
725
- };
726
- }
727
- return {
728
- ok: false,
729
- error: Errors.providerError("better-auth", "sendOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
730
- };
731
- } catch (err) {
732
- return {
733
- ok: false,
734
- error: mapFetchError$1("sendOtp", err, options?.signal)
735
- };
736
- }
737
- }
738
- async verifyOtp(input, options) {
739
- if (options?.signal?.aborted) return {
740
- ok: false,
741
- error: Errors.cancelled({ operation: "verifyOtp" })
742
- };
743
- try {
744
- const res = await this.fetchImpl(this.url("/api/auth/sign-in/email-otp"), withSignal$1({
745
- method: "POST",
746
- headers: { "Content-Type": "application/json" },
747
- body: JSON.stringify({
748
- email: input.email,
749
- otp: input.otp
750
- }),
751
- credentials: "include"
752
- }, options?.signal));
753
- if (options?.signal?.aborted) return {
754
- ok: false,
755
- error: Errors.cancelled({ operation: "verifyOtp" })
756
- };
757
- const body = await safeJson$1(res);
758
- if (res.ok) {
759
- if (typeof body === "object" && body !== null) {
760
- const okBody = body;
761
- if (typeof okBody.token === "string" && typeof okBody.user === "object" && okBody.user !== null) return {
762
- ok: true,
763
- value: authSessionFromBetterAuth$1(okBody.token, okBody.user)
764
- };
765
- }
766
- return {
767
- ok: false,
768
- error: Errors.providerError("better-auth", "verifyOtp", /* @__PURE__ */ new Error("unexpected 200 body"))
769
- };
770
- }
771
- return {
772
- ok: false,
773
- error: mapBetterAuthError("verifyOtp", body)
774
- };
775
- } catch (err) {
776
- return {
777
- ok: false,
778
- error: mapFetchError$1("verifyOtp", err, options?.signal)
779
- };
780
- }
781
- }
782
- async getSession(options) {
783
- if (options?.signal?.aborted) return {
784
- ok: false,
785
- error: Errors.cancelled({ operation: "getSession" })
786
- };
787
- try {
788
- const res = await this.fetchImpl(this.url("/api/auth/get-session"), withSignal$1({
789
- method: "GET",
790
- credentials: "include"
791
- }, options?.signal));
792
- if (options?.signal?.aborted) return {
793
- ok: false,
794
- error: Errors.cancelled({ operation: "getSession" })
795
- };
796
- if (!res.ok) return {
797
- ok: false,
798
- error: Errors.providerError("better-auth", "getSession", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
799
- };
800
- const body = await safeJson$1(res);
801
- if (body === null) return {
802
- ok: true,
803
- value: null
804
- };
805
- if (typeof body === "object" && body !== null) {
806
- const okBody = body;
807
- if (typeof okBody.user === "object" && okBody.user !== null) return {
808
- ok: true,
809
- value: authSessionFromBetterAuth$1(okBody.session?.token ?? okBody.session?.id ?? "session", okBody.user)
810
- };
811
- }
812
- return {
813
- ok: true,
814
- value: null
815
- };
816
- } catch (err) {
817
- return {
818
- ok: false,
819
- error: mapFetchError$1("getSession", err, options?.signal)
820
- };
821
- }
822
- }
823
- async signOut(options) {
824
- if (options?.signal?.aborted) return {
825
- ok: false,
826
- error: Errors.cancelled({ operation: "signOut" })
827
- };
828
- try {
829
- const res = await this.fetchImpl(this.url("/api/auth/sign-out"), withSignal$1({
830
- method: "POST",
831
- headers: { "Content-Type": "application/json" },
832
- body: "{}",
833
- credentials: "include"
834
- }, options?.signal));
835
- if (options?.signal?.aborted) return {
836
- ok: false,
837
- error: Errors.cancelled({ operation: "signOut" })
838
- };
839
- if (res.ok) return {
840
- ok: true,
841
- value: void 0
842
- };
843
- return {
844
- ok: false,
845
- error: Errors.providerError("better-auth", "signOut", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
846
- };
847
- } catch (err) {
848
- return {
849
- ok: false,
850
- error: mapFetchError$1("signOut", err, options?.signal)
851
- };
852
- }
853
- }
854
- async getConvexJwt(options) {
855
- if (options?.signal?.aborted) return {
856
- ok: false,
857
- error: Errors.cancelled({ operation: "getConvexJwt" })
858
- };
859
- try {
860
- const res = await this.fetchImpl(this.url("/api/auth/convex/token"), withSignal$1({
861
- method: "GET",
862
- credentials: "include"
863
- }, options?.signal));
864
- if (options?.signal?.aborted) return {
865
- ok: false,
866
- error: Errors.cancelled({ operation: "getConvexJwt" })
867
- };
868
- if (res.status === 401) return {
869
- ok: false,
870
- error: Errors.notAuthenticated()
871
- };
872
- if (!res.ok) return {
873
- ok: false,
874
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
875
- };
876
- const body = await safeJson$1(res);
877
- if (typeof body === "object" && body !== null) {
878
- const okBody = body;
879
- if (typeof okBody.token === "string" && okBody.token.length > 0) return {
880
- ok: true,
881
- value: {
882
- token: toJwtToken(okBody.token),
883
- expEpochSeconds: toEpochSeconds(decodeJwtExp$1(okBody.token))
884
- }
885
- };
886
- }
887
- return {
888
- ok: false,
889
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error("unexpected body"))
890
- };
891
- } catch (err) {
892
- return {
893
- ok: false,
894
- error: mapFetchError$1("getConvexJwt", err, options?.signal)
895
- };
896
- }
897
- }
898
- };
899
- function BetterAuthBrowserLayer(deps) {
900
- return Layer.succeed(AuthClientPortTag, authClientPortFromPromiseAdapter(new BetterAuthBrowserAdapter(deps)));
901
- }
902
- //#endregion
903
- //#region src/adapters/auth-client/cookie-jar.ts
904
- function parseSetCookie(raw) {
905
- const parts = raw.split(";").map((p) => p.trim());
906
- if (parts.length === 0 || parts[0] === void 0) return null;
907
- const nameValue = parts[0];
908
- const eq = nameValue.indexOf("=");
909
- if (eq < 0) return null;
910
- const name = nameValue.slice(0, eq).trim();
911
- const value = nameValue.slice(eq + 1).trim();
912
- if (name.length === 0) return null;
913
- let maxAgeSeconds = null;
914
- let expiresEpochMs = null;
915
- let path = null;
916
- let httpOnly = false;
917
- let secure = false;
918
- let sameSite = null;
919
- for (let i = 1; i < parts.length; i++) {
920
- const part = parts[i];
921
- if (part === void 0) continue;
922
- const partEq = part.indexOf("=");
923
- const key = (partEq < 0 ? part : part.slice(0, partEq)).trim().toLowerCase();
924
- const val = partEq < 0 ? "" : part.slice(partEq + 1).trim();
925
- if (key === "max-age") {
926
- const n = Number(val);
927
- if (Number.isFinite(n)) maxAgeSeconds = n;
928
- } else if (key === "expires") {
929
- const t = Date.parse(val);
930
- if (Number.isFinite(t)) expiresEpochMs = t;
931
- } else if (key === "path") path = val;
932
- else if (key === "httponly") httpOnly = true;
933
- else if (key === "secure") secure = true;
934
- else if (key === "samesite") {
935
- const lc = val.toLowerCase();
936
- if (lc === "strict" || lc === "lax" || lc === "none") sameSite = lc;
937
- }
938
- }
939
- return {
940
- name,
941
- value,
942
- maxAgeSeconds,
943
- expiresEpochMs,
944
- path,
945
- httpOnly,
946
- secure,
947
- sameSite
948
- };
949
- }
950
- function isExpired(cookie, nowEpochMs) {
951
- if (cookie.maxAgeSeconds !== null) {
952
- if (cookie.maxAgeSeconds <= 0) return true;
953
- return nowEpochMs >= cookie.storedAtEpochMs + cookie.maxAgeSeconds * 1e3;
954
- }
955
- if (cookie.expiresEpochMs !== null) return nowEpochMs >= cookie.expiresEpochMs;
956
- return false;
957
- }
958
- var CookieJar = class {
959
- store = /* @__PURE__ */ new Map();
960
- set(host, setCookieHeaders) {
961
- let perHost = this.store.get(host);
962
- const now = Date.now();
963
- for (const raw of setCookieHeaders) {
964
- const parsed = parseSetCookie(raw);
965
- if (parsed === null) continue;
966
- if (perHost === void 0) {
967
- perHost = /* @__PURE__ */ new Map();
968
- this.store.set(host, perHost);
969
- }
970
- if (parsed.maxAgeSeconds !== null && parsed.maxAgeSeconds <= 0) {
971
- perHost.delete(parsed.name);
972
- continue;
973
- }
974
- perHost.set(parsed.name, {
975
- ...parsed,
976
- storedAtEpochMs: now
977
- });
978
- }
979
- if (perHost !== void 0 && perHost.size === 0) this.store.delete(host);
980
- }
981
- getCookieHeader(host) {
982
- const perHost = this.store.get(host);
983
- if (perHost === void 0 || perHost.size === 0) return null;
984
- const now = Date.now();
985
- const live = [];
986
- for (const [name, cookie] of perHost.entries()) {
987
- if (isExpired(cookie, now)) {
988
- perHost.delete(name);
989
- continue;
990
- }
991
- live.push(`${name}=${cookie.value}`);
992
- }
993
- if (live.length === 0) {
994
- this.store.delete(host);
995
- return null;
996
- }
997
- return live.join("; ");
998
- }
999
- };
1000
- //#endregion
1001
- //#region src/adapters/auth-client/BetterAuthNodeAdapter.ts
1002
- function withSignal(init, signal) {
1003
- return signal === void 0 ? init : {
1004
- ...init,
1005
- signal
1006
- };
1007
- }
1008
- const DEFAULT_JWT_LIFETIME_S = 900;
1009
- function decodeJwtExp(jwt) {
1010
- const parts = jwt.split(".");
1011
- if (parts.length < 2 || parts[1] === void 0) return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S;
1012
- try {
1013
- const raw = parts[1].replace(/\s+/g, "");
1014
- const pad = "=".repeat((4 - raw.length % 4) % 4);
1015
- const decoded = Buffer.from(raw.replace(/-/g, "+").replace(/_/g, "/") + pad, "base64").toString("utf-8");
1016
- const payload = JSON.parse(decoded);
1017
- if (typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > 0) return payload.exp;
1018
- } catch {}
1019
- return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S;
1020
- }
1021
- function authSessionFromBetterAuth(token, user) {
1022
- return {
1023
- authUserId: toAuthUserId(user.id),
1024
- email: toEmail(user.email),
1025
- token: toSessionToken(token),
1026
- expiresAt: toEpochMs(Date.now() + 6048e5)
1027
- };
1028
- }
1029
- async function safeJson(res) {
1030
- const raw = await res.text();
1031
- if (raw.length === 0 || raw === "null") return null;
1032
- try {
1033
- return JSON.parse(raw);
1034
- } catch {
1035
- return null;
1036
- }
1037
- }
1038
- function hostFromBaseUrl(baseUrl) {
1039
- try {
1040
- return new URL(baseUrl).host;
1041
- } catch {
1042
- return baseUrl;
1043
- }
1044
- }
1045
- function originFromBaseUrl(baseUrl) {
1046
- try {
1047
- return new URL(baseUrl).origin;
1048
- } catch {
1049
- return;
1050
- }
1051
- }
1052
- function setCookiesFromResponse(jar, host, res) {
1053
- const ext = res.headers;
1054
- let setCookies = [];
1055
- if (typeof ext.getSetCookie === "function") setCookies = ext.getSetCookie();
1056
- else res.headers.forEach((value, key) => {
1057
- if (key.toLowerCase() === "set-cookie") setCookies.push(value);
1058
- });
1059
- if (setCookies.length > 0) jar.set(host, setCookies);
1060
- }
1061
- function isAbortError(err, signal) {
1062
- return signal?.aborted === true || err instanceof Error && err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError";
1063
- }
1064
- function mapFetchError(operation, err, signal) {
1065
- if (isAbortError(err, signal)) return Errors.cancelled({ operation });
1066
- return Errors.networkError(operation, err instanceof Error ? err : new Error(String(err)));
1067
- }
1068
- var BetterAuthNodeAdapter = class {
1069
- authBaseUrl;
1070
- host;
1071
- origin;
1072
- cookieJar;
1073
- fetchImpl;
1074
- observation;
1075
- constructor(deps) {
1076
- this.authBaseUrl = deps.authBaseUrl.replace(/\/$/, "");
1077
- this.host = hostFromBaseUrl(this.authBaseUrl);
1078
- this.origin = deps.origin?.replace(/\/$/, "");
1079
- this.cookieJar = deps.cookieJar ?? new CookieJar();
1080
- this.fetchImpl = deps.fetch ?? fetch;
1081
- this.observation = deps.observation;
1082
- }
1083
- url(path) {
1084
- return resolveAuthClientUrl(this.authBaseUrl, path);
1085
- }
1086
- headersWithCookie(extra = {}) {
1087
- const cookie = this.cookieJar.getCookieHeader(this.host);
1088
- return cookie !== null ? {
1089
- ...extra,
1090
- cookie
1091
- } : { ...extra };
1092
- }
1093
- async canSendOtp(_input, options) {
1094
- if (options?.signal?.aborted) return {
1095
- ok: false,
1096
- error: Errors.cancelled({ operation: "canSendOtp" })
1097
- };
1098
- return {
1099
- ok: true,
1100
- value: {
1101
- allowed: true,
1102
- cooldownMs: toDurationMs(0)
1103
- }
1104
- };
1105
- }
1106
- async sendOtp(input, options) {
1107
- if (options?.signal?.aborted) return {
1108
- ok: false,
1109
- error: Errors.cancelled({ operation: "sendOtp" })
1110
- };
1111
- try {
1112
- const res = await this.fetchImpl(this.url("/api/auth/email-otp/send-verification-otp"), withSignal({
1113
- method: "POST",
1114
- headers: {
1115
- "content-type": "application/json",
1116
- ...this.origin ? { origin: this.origin } : {},
1117
- ...observationRequestHeaders(this.observation, input)
1118
- },
1119
- body: JSON.stringify({
1120
- email: input.email,
1121
- type: "sign-in"
1122
- })
1123
- }, options?.signal));
1124
- if (options?.signal?.aborted) return {
1125
- ok: false,
1126
- error: Errors.cancelled({ operation: "sendOtp" })
1127
- };
1128
- if (res.ok) return {
1129
- ok: true,
1130
- value: void 0
1131
- };
1132
- if (res.status === 429) return {
1133
- ok: false,
1134
- error: Errors.rateLimited({ resource: "better-auth/sendOtp" })
1135
- };
1136
- const body = await safeJson(res);
1137
- if (typeof body === "object" && body !== null) {
1138
- const errBody = body;
1139
- if (errBody.code === "INVALID_EMAIL" || errBody.code === "VALIDATION_ERROR") return {
1140
- ok: false,
1141
- error: Errors.invalidInput("email", errBody.message ?? "invalid email")
1142
- };
1143
- }
1144
- return {
1145
- ok: false,
1146
- error: Errors.providerError("better-auth", "sendOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
1147
- };
1148
- } catch (err) {
1149
- return {
1150
- ok: false,
1151
- error: mapFetchError("sendOtp", err, options?.signal)
1152
- };
1153
- }
1154
- }
1155
- async verifyOtp(input, options) {
1156
- if (options?.signal?.aborted) return {
1157
- ok: false,
1158
- error: Errors.cancelled({ operation: "verifyOtp" })
1159
- };
1160
- try {
1161
- const res = await this.fetchImpl(this.url("/api/auth/sign-in/email-otp"), withSignal({
1162
- method: "POST",
1163
- headers: {
1164
- "content-type": "application/json",
1165
- ...this.origin ? { origin: this.origin } : {}
1166
- },
1167
- body: JSON.stringify({
1168
- email: input.email,
1169
- otp: input.otp
1170
- })
1171
- }, options?.signal));
1172
- if (options?.signal?.aborted) return {
1173
- ok: false,
1174
- error: Errors.cancelled({ operation: "verifyOtp" })
1175
- };
1176
- setCookiesFromResponse(this.cookieJar, this.host, res);
1177
- const body = await safeJson(res);
1178
- if (res.ok && typeof body === "object" && body !== null) {
1179
- const okBody = body;
1180
- if (typeof okBody.token === "string" && typeof okBody.user === "object" && okBody.user !== null) return {
1181
- ok: true,
1182
- value: authSessionFromBetterAuth(okBody.token, okBody.user)
1183
- };
1184
- }
1185
- if (!res.ok) {
1186
- if (typeof body === "object" && body !== null) {
1187
- const errBody = body;
1188
- if (errBody.code === "OTP_EXPIRED") return {
1189
- ok: false,
1190
- error: Errors.otpExpired()
1191
- };
1192
- if (errBody.code === "INVALID_OTP") return {
1193
- ok: false,
1194
- error: Errors.invalidInput("otp", errBody.message ?? "invalid OTP")
1195
- };
1196
- }
1197
- }
1198
- return {
1199
- ok: false,
1200
- error: Errors.providerError("better-auth", "verifyOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
1201
- };
1202
- } catch (err) {
1203
- return {
1204
- ok: false,
1205
- error: mapFetchError("verifyOtp", err, options?.signal)
1206
- };
1207
- }
1208
- }
1209
- async getSession(options) {
1210
- if (options?.signal?.aborted) return {
1211
- ok: false,
1212
- error: Errors.cancelled({ operation: "getSession" })
1213
- };
1214
- try {
1215
- const res = await this.fetchImpl(this.url("/api/auth/get-session"), withSignal({
1216
- method: "GET",
1217
- headers: this.headersWithCookie()
1218
- }, options?.signal));
1219
- if (options?.signal?.aborted) return {
1220
- ok: false,
1221
- error: Errors.cancelled({ operation: "getSession" })
1222
- };
1223
- if (!res.ok) return {
1224
- ok: false,
1225
- error: Errors.providerError("better-auth", "getSession", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
1226
- };
1227
- const body = await safeJson(res);
1228
- if (body === null) return {
1229
- ok: true,
1230
- value: null
1231
- };
1232
- if (typeof body === "object" && body !== null) {
1233
- const okBody = body;
1234
- if (typeof okBody.user === "object" && okBody.user !== null) return {
1235
- ok: true,
1236
- value: authSessionFromBetterAuth(okBody.session?.token ?? okBody.session?.id ?? "session", okBody.user)
1237
- };
1238
- }
1239
- return {
1240
- ok: true,
1241
- value: null
1242
- };
1243
- } catch (err) {
1244
- return {
1245
- ok: false,
1246
- error: mapFetchError("getSession", err, options?.signal)
1247
- };
1248
- }
1249
- }
1250
- async signOut(options) {
1251
- if (options?.signal?.aborted) return {
1252
- ok: false,
1253
- error: Errors.cancelled({ operation: "signOut" })
1254
- };
1255
- try {
1256
- const signOutOrigin = originFromBaseUrl(this.authBaseUrl) ?? this.origin;
1257
- const res = await this.fetchImpl(this.url("/api/auth/sign-out"), withSignal({
1258
- method: "POST",
1259
- headers: this.headersWithCookie({
1260
- "content-type": "application/json",
1261
- ...signOutOrigin ? { origin: signOutOrigin } : {}
1262
- }),
1263
- body: "{}"
1264
- }, options?.signal));
1265
- if (options?.signal?.aborted) return {
1266
- ok: false,
1267
- error: Errors.cancelled({ operation: "signOut" })
1268
- };
1269
- setCookiesFromResponse(this.cookieJar, this.host, res);
1270
- if (res.ok) return {
1271
- ok: true,
1272
- value: void 0
1273
- };
1274
- return {
1275
- ok: false,
1276
- error: Errors.providerError("better-auth", "signOut", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
1277
- };
1278
- } catch (err) {
1279
- return {
1280
- ok: false,
1281
- error: mapFetchError("signOut", err, options?.signal)
1282
- };
1283
- }
1284
- }
1285
- async getConvexJwt(options) {
1286
- if (options?.signal?.aborted) return {
1287
- ok: false,
1288
- error: Errors.cancelled({ operation: "getConvexJwt" })
1289
- };
1290
- try {
1291
- const res = await this.fetchImpl(this.url("/api/auth/convex/token"), withSignal({
1292
- method: "GET",
1293
- headers: this.headersWithCookie()
1294
- }, options?.signal));
1295
- if (options?.signal?.aborted) return {
1296
- ok: false,
1297
- error: Errors.cancelled({ operation: "getConvexJwt" })
1298
- };
1299
- if (res.status === 401) return {
1300
- ok: false,
1301
- error: Errors.notAuthenticated()
1302
- };
1303
- if (!res.ok) return {
1304
- ok: false,
1305
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
1306
- };
1307
- const body = await safeJson(res);
1308
- if (typeof body === "object" && body !== null) {
1309
- const okBody = body;
1310
- if (typeof okBody.token === "string" && okBody.token.length > 0) return {
1311
- ok: true,
1312
- value: {
1313
- token: toJwtToken(okBody.token),
1314
- expEpochSeconds: toEpochSeconds(decodeJwtExp(okBody.token))
1315
- }
1316
- };
1317
- }
1318
- return {
1319
- ok: false,
1320
- error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error("unexpected body"))
1321
- };
1322
- } catch (err) {
1323
- return {
1324
- ok: false,
1325
- error: mapFetchError("getConvexJwt", err, options?.signal)
1326
- };
1327
- }
1328
- }
1329
- };
1330
- function BetterAuthNodeLayer(deps) {
1331
- return Layer.succeed(AuthClientPortTag, authClientPortFromPromiseAdapter(new BetterAuthNodeAdapter(deps)));
1332
- }
1333
- //#endregion
1334
- //#region src/adapters/bootstrap/HttpBootstrapAdapter.ts
1335
- const DEFAULT_RETRY = {
1336
- attempts: 2,
1337
- baseDelayMs: 400
1338
- };
1339
- /** Request-side statuses a proxy or backend returns while momentarily unable to serve. */
1340
- const TRANSIENT_HTTP_STATUSES = /* @__PURE__ */ new Set([
1341
- 408,
1342
- 425,
1343
- 429
1344
- ]);
1345
- /** Every 5xx is a server-side condition worth one more try; the client sent nothing wrong. */
1346
- function isTransientHttpStatus(status) {
1347
- return status >= 500 || TRANSIENT_HTTP_STATUSES.has(status);
1348
- }
1349
- /**
1350
- * A transient HTTP status is worth one more try. The bootstrap call crosses
1351
- * the host's own proxy before it reaches Convex, and every observed failure
1352
- * of that hop cleared on a retry seconds later. A network rejection (offline,
1353
- * DNS, CORS) is not retried: it does not clear in a second, and the caller
1354
- * surfaces it as `NETWORK_ERROR` at once. Auth and input rejections are
1355
- * deterministic and never retried.
1356
- */
1357
- function isTransientBootstrapFailure(error) {
1358
- if (error.kind !== "provider") return false;
1359
- const status = error.details?.httpStatus;
1360
- return typeof status === "number" && isTransientHttpStatus(status);
1361
- }
1362
- async function safeText(res) {
1363
- try {
1364
- return await res.text();
1365
- } catch {
1366
- return "";
1367
- }
1368
- }
1369
- var HttpBootstrapAdapter = class {
1370
- bootstrapBaseUrl;
1371
- bootstrapHost;
1372
- fetchImpl;
1373
- observation;
1374
- retry;
1375
- constructor(deps) {
1376
- this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
1377
- this.bootstrapHost = hostOf(this.bootstrapBaseUrl);
1378
- this.observation = deps.observation;
1379
- this.retry = deps.retry ?? DEFAULT_RETRY;
1380
- this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
1381
- }
1382
- resolve(input) {
1383
- return this.attempt(input).pipe(Effect.retry({
1384
- times: this.retry.attempts,
1385
- while: isTransientBootstrapFailure,
1386
- schedule: Schedule.exponential(Duration.millis(this.retry.baseDelayMs))
1387
- }));
1388
- }
1389
- attempt(input) {
1390
- return Effect.tryPromise({
1391
- try: () => {
1392
- const headers = {
1393
- "content-type": "application/json",
1394
- ...input.origin === void 0 ? {} : { origin: input.origin },
1395
- ...observationRequestHeaders(this.observation)
1396
- };
1397
- return this.fetchImpl(`${this.bootstrapBaseUrl}/v1/client/bootstrap`, {
1398
- method: "POST",
1399
- headers,
1400
- body: JSON.stringify({ publishableKey: input.publishableKey })
1401
- });
1402
- },
1403
- catch: (cause) => bootstrapErrorFromCapxul("network", Errors.networkError("bootstrap", cause, {
1404
- provider: "bootstrap",
1405
- failure_mode: "upstream-down",
1406
- ...this.bootstrapHost === void 0 ? {} : { target_host: this.bootstrapHost }
1407
- }))
1408
- }).pipe(Effect.flatMap((res) => this.mapResponse(res)));
1409
- }
1410
- mapResponse(res) {
1411
- if (res.ok) return Effect.tryPromise({
1412
- try: async () => {
1413
- const body = await res.json();
1414
- const state = typeof body === "object" && body !== null && "state" in body && typeof body.state === "object" && body.state !== null ? body.state : void 0;
1415
- const rawEngineeringTelemetry = state?.engineeringTelemetry;
1416
- const baseBody = state === void 0 || rawEngineeringTelemetry === void 0 ? body : {
1417
- ...body,
1418
- state: Object.fromEntries(Object.entries(state).filter(([key]) => key !== "engineeringTelemetry"))
1419
- };
1420
- const decoded = SchemaParser.decodeUnknownResult(BootstrapEnvelope)(baseBody);
1421
- if (Result.isFailure(decoded)) throw Errors.invalidInput("bootstrapEnvelope", SchemaIssue.makeFormatterDefault()(decoded.failure));
1422
- const { state: decodedState } = decoded.success;
1423
- const decodedEngineeringTelemetry = rawEngineeringTelemetry === void 0 ? void 0 : SchemaParser.decodeUnknownResult(EngineeringTelemetryBootstrapPolicy)(rawEngineeringTelemetry, { onExcessProperty: "error" });
1424
- return {
1425
- applicationId: decodedState.applicationId,
1426
- chainId: decodedState.chainId,
1427
- sessionToken: decodedState.sessionToken,
1428
- issuedAt: decodedState.issuedAt,
1429
- expiresIn: decodedState.expiresIn,
1430
- authBaseUrl: normalizeRuntimeUrl("authBaseUrl", decodedState.authBaseUrl),
1431
- convexUrl: normalizeRuntimeUrl("convexUrl", decodedState.convexUrl),
1432
- siteBaseUrl: normalizeRuntimeUrl("siteBaseUrl", decodedState.siteBaseUrl),
1433
- openfortPublishableKey: decodedState.openfortPublishableKey,
1434
- shieldPublishableKey: decodedState.shieldPublishableKey,
1435
- ...decodedEngineeringTelemetry !== void 0 && Result.isSuccess(decodedEngineeringTelemetry) ? { engineeringTelemetry: decodedEngineeringTelemetry.success } : {}
1436
- };
1437
- },
1438
- catch: (cause) => {
1439
- if (cause instanceof CapxulError && cause.code === "INVALID_INPUT") return bootstrapErrorFromCapxul("invalidInput", cause);
1440
- return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
1441
- }
1442
- });
1443
- return Effect.promise(() => safeText(res)).pipe(Effect.flatMap((body) => {
1444
- if (res.status === 401 || body.startsWith("NOT_AUTHENTICATED")) return Effect.fail(bootstrapErrorFromCapxul("notAuthenticated", Errors.notAuthenticated()));
1445
- if (res.status === 400 || body.startsWith("INVALID_INPUT")) return Effect.fail(bootstrapErrorFromCapxul("invalidInput", Errors.invalidInput("publishableKey", "rejected by bootstrap")));
1446
- const responseBody = body.slice(0, 300);
1447
- const edgeError = res.headers?.get?.("x-vercel-error") ?? void 0;
1448
- const edgeRequestId = res.headers?.get?.("x-vercel-id") ?? void 0;
1449
- return Effect.fail(bootstrapErrorFromCapxul("provider", Errors.providerError("convex", "bootstrap", /* @__PURE__ */ new Error(`HTTP ${res.status}${edgeError === void 0 ? "" : ` ${edgeError}`}`), {
1450
- httpStatus: res.status,
1451
- details: {
1452
- ...responseBody.length === 0 ? {} : { responseBody },
1453
- ...edgeError === void 0 ? {} : { edgeError },
1454
- ...edgeRequestId === void 0 ? {} : { edgeRequestId }
1455
- }
1456
- })));
1457
- }));
1458
- }
1459
- };
1460
- function HttpBootstrapLayer(deps) {
1461
- return Layer.succeed(BootstrapPortTag, new HttpBootstrapAdapter(deps));
1462
- }
1463
- function hostOf(url) {
1464
- try {
1465
- return new URL(url).hostname;
1466
- } catch {
1467
- return;
1468
- }
1469
- }
1470
- function normalizeRuntimeUrl(field, raw) {
1471
- let parsed;
1472
- try {
1473
- parsed = new URL(raw);
1474
- } catch {
1475
- throw Errors.invalidInput(field, "must be an http or https URL");
1476
- }
1477
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
1478
- return parsed.toString().replace(/\/$/, "");
1479
- }
1480
- //#endregion
1481
- //#region src/adapters/convex-call/convex-connection-monitor.ts
1482
- var ConvexConnectionMonitor = class {
1483
- #connectionId = makeConnectionId();
1484
- #browser = new BrowserLifecycleMonitor();
1485
- #lastClose;
1486
- observedWebSocketConstructor() {
1487
- const NativeWebSocket = globalThis.WebSocket;
1488
- if (typeof globalThis.window === "undefined" || NativeWebSocket === void 0) return void 0;
1489
- return new Proxy(NativeWebSocket, { construct: (Target, args) => {
1490
- const socket = Reflect.construct(Target, args);
1491
- socket.addEventListener("close", (event) => {
1492
- this.#lastClose = this.#browser.socketClose(event);
1493
- });
1494
- return socket;
1495
- } });
1496
- }
1497
- close() {
1498
- this.#browser.close();
1499
- }
1500
- observe(client, observer) {
1501
- const subscribe = client.subscribeToConnectionState?.bind(client);
1502
- if (subscribe === void 0) return () => this.#browser.close();
1503
- let previous;
1504
- const unsubscribe = subscribe((next) => {
1505
- const diagnostics = this.#diagnostics(previous, next);
1506
- previous = next;
1507
- for (const diagnostic of diagnostics) emitDiagnostic(observer, diagnostic);
1508
- });
1509
- if (previous === void 0) {
1510
- const current = safeConnectionState(client);
1511
- if (current !== void 0) {
1512
- previous = current;
1513
- for (const diagnostic of this.#diagnostics(void 0, current)) emitDiagnostic(observer, diagnostic);
1514
- }
1515
- }
1516
- let closed = false;
1517
- return () => {
1518
- if (closed) return;
1519
- closed = true;
1520
- try {
1521
- unsubscribe();
1522
- } finally {
1523
- this.#browser.close();
1524
- }
1525
- };
1526
- }
1527
- reconnectEvidence(start, end) {
1528
- if (!connectionRestarted(start, end)) return void 0;
1529
- return {
1530
- kind: "connection-lost-in-flight",
1531
- connection_id: this.#connectionId,
1532
- start_connection_count: start.connectionCount,
1533
- connection_count: end.connectionCount,
1534
- ...end.connectionRetries === void 0 ? {} : { connection_retries: end.connectionRetries },
1535
- ...closeFields(this.#lastClose)
1536
- };
1537
- }
1538
- #diagnostics(previous, next) {
1539
- const transitions = connectionTransitions(previous, next);
1540
- if (transitions.length === 0) return [];
1541
- const oldest = next.timeOfOldestInflightRequest;
1542
- const oldestInflightMs = oldest instanceof Date && Number.isFinite(oldest.getTime()) ? Math.max(0, Date.now() - oldest.getTime()) : void 0;
1543
- return transitions.map((transition) => ({
1544
- connection_id: this.#connectionId,
1545
- transition,
1546
- previous_connected: previous?.isWebSocketConnected ?? false,
1547
- connected: next.isWebSocketConnected,
1548
- ...next.hasEverConnected === void 0 ? {} : { has_ever_connected: next.hasEverConnected },
1549
- connection_count: next.connectionCount,
1550
- ...next.connectionRetries === void 0 ? {} : { connection_retries: next.connectionRetries },
1551
- ...next.hasInflightRequests === void 0 ? {} : { has_inflight_requests: next.hasInflightRequests },
1552
- ...next.inflightActions === void 0 ? {} : { inflight_actions: next.inflightActions },
1553
- ...next.inflightMutations === void 0 ? {} : { inflight_mutations: next.inflightMutations },
1554
- ...oldestInflightMs === void 0 ? {} : { oldest_inflight_ms: oldestInflightMs },
1555
- ...closeFields(this.#lastClose)
1556
- }));
1557
- }
1558
- };
1559
- function connectionRestarted(start, end) {
1560
- if (start === void 0 || end === void 0) return false;
1561
- const opensBeforeCancellation = start.isWebSocketConnected ? 1 : 2;
1562
- return end.connectionCount >= start.connectionCount + opensBeforeCancellation;
1563
- }
1564
- function connectionTransitions(previous, next) {
1565
- if (previous === void 0) {
1566
- if (next.isWebSocketConnected) return (next.connectionRetries ?? 0) > 0 ? ["retrying", "connected"] : ["connected"];
1567
- return (next.connectionRetries ?? 0) > 0 ? ["disconnected", "retrying"] : [];
1568
- }
1569
- const transitions = [];
1570
- if (previous.isWebSocketConnected !== next.isWebSocketConnected) transitions.push(next.isWebSocketConnected ? "connected" : "disconnected");
1571
- if ((next.connectionRetries ?? 0) > (previous.connectionRetries ?? 0)) transitions.push("retrying");
1572
- return transitions;
1573
- }
1574
- function emitDiagnostic(observer, diagnostic) {
1575
- try {
1576
- observer(diagnostic);
1577
- } catch {}
1578
- }
1579
- function safeConnectionState(client) {
1580
- try {
1581
- return client.connectionState?.();
1582
- } catch {
1583
- return;
1584
- }
1585
- }
1586
- function closeFields(close) {
1587
- if (close === void 0) return {};
1588
- return {
1589
- close_code: close.code,
1590
- close_was_clean: close.wasClean,
1591
- close_reason_present: close.reasonPresent,
1592
- ...close.documentVisibility === void 0 ? {} : { document_visibility: close.documentVisibility },
1593
- ...close.navigatorOnline === void 0 ? {} : { navigator_online: close.navigatorOnline },
1594
- ...close.browserEvent === void 0 ? {} : { last_browser_event: close.browserEvent },
1595
- ...close.msSinceBrowserEvent === void 0 ? {} : { ms_since_browser_event: close.msSinceBrowserEvent }
1596
- };
1597
- }
1598
- var BrowserLifecycleMonitor = class {
1599
- #listeners = [];
1600
- #last = {};
1601
- constructor() {
1602
- const windowTarget = eventTarget(globalThis.window);
1603
- const documentTarget = eventTarget(globalThis.document);
1604
- if (windowTarget === void 0 || documentTarget === void 0) return;
1605
- this.#listen(documentTarget, "visibilitychange", () => {
1606
- this.#record(globalThis.document?.visibilityState === "hidden" ? "visibility-hidden" : "visibility-visible");
1607
- });
1608
- this.#listen(windowTarget, "pagehide", () => this.#record("pagehide"));
1609
- this.#listen(windowTarget, "pageshow", () => this.#record("pageshow"));
1610
- this.#listen(windowTarget, "online", () => this.#record("online"));
1611
- this.#listen(windowTarget, "offline", () => this.#record("offline"));
1612
- }
1613
- socketClose(event) {
1614
- const now = Date.now();
1615
- return {
1616
- code: event.code,
1617
- wasClean: event.wasClean,
1618
- reasonPresent: event.reason.length > 0,
1619
- ...this.#last.event === void 0 ? {} : { browserEvent: this.#last.event },
1620
- ...this.#last.at === void 0 ? {} : { msSinceBrowserEvent: Math.max(0, now - this.#last.at) },
1621
- ...globalThis.document?.visibilityState === void 0 ? {} : { documentVisibility: globalThis.document.visibilityState },
1622
- ...globalThis.navigator?.onLine === void 0 ? {} : { navigatorOnline: globalThis.navigator.onLine }
1623
- };
1624
- }
1625
- close() {
1626
- for (const [target, name, listener] of this.#listeners) target.removeEventListener(name, listener);
1627
- this.#listeners.length = 0;
1628
- }
1629
- #listen(target, name, listener) {
1630
- target.addEventListener(name, listener);
1631
- this.#listeners.push([
1632
- target,
1633
- name,
1634
- listener
1635
- ]);
1636
- }
1637
- #record(event) {
1638
- this.#last = {
1639
- event,
1640
- at: Date.now()
1641
- };
1642
- }
1643
- };
1644
- function eventTarget(value) {
1645
- if (typeof value !== "object" || value === null || !("addEventListener" in value) || typeof value.addEventListener !== "function" || !("removeEventListener" in value) || typeof value.removeEventListener !== "function") return;
1646
- return value;
1647
- }
1648
- let fallbackConnectionId = 0;
1649
- function makeConnectionId() {
1650
- try {
1651
- return `convex_${globalThis.crypto.randomUUID()}`;
1652
- } catch {
1653
- fallbackConnectionId += 1;
1654
- return `convex_${Date.now().toString(36)}_${fallbackConnectionId.toString(36)}`;
1655
- }
1656
- }
1657
- //#endregion
1658
- //#region src/adapters/convex-call/ConvexCallAdapter.ts
1659
- /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
1660
- const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
1661
- "payroll/actions:authorizeRun",
1662
- "account/actions:readBalance",
1663
- "holdings/actions:current",
1664
- "smartAccount/actions:claim",
1665
- "org/actions:prepareFounderAccount",
1666
- "org/actions:prepareBootstrap",
1667
- "org/actions:submitBootstrap",
1668
- "org/actions:resumeBootstrapSubmission",
1669
- "org/actions:confirmBootstrap",
1670
- "org/actions:readTreasury",
1671
- "moneyExecution/actions:preparePaymentExecution",
1672
- "moneyExecution/actions:abandonPaymentExecution",
1673
- "moneyExecution/actions:submitPaymentExecution",
1674
- "moneyExecution/paymentCommandActions:preparePaymentLifecycleExecution",
1675
- "moneyExecution/paymentCommandActions:prepareOrganizationPaymentExecution",
1676
- "moneyExecution/paymentCommandActions:submitPaymentCommandExecution"
1677
- ]);
1678
- const OBSERVED_CONVEX_QUERIES = /* @__PURE__ */ new Set([
1679
- "movement/activity:list",
1680
- "movement/activity:summary",
1681
- "movement/activity:get",
1682
- "financialOps/queries:depositInstructions",
1683
- "financialOps/destinations:list",
1684
- "financialOps/queries:verifyPaymentDocument",
1685
- "financialOps/queries:renderStoredDocument",
1686
- "financialOps/addressBook:list",
1687
- "financialOps/addressBook:get",
1688
- "financialOps/requestsInbox:list",
1689
- "financialOps/requestsInbox:get",
1690
- "financialOps/requestsInbox:inboxList",
1691
- "payroll/queries:runs",
1692
- "payroll/queries:groups",
1693
- "payroll/queries:terms",
1694
- "financialOps/queries:resolveRecipient",
1695
- "identity/queries:loadByAuthUserId",
1696
- "smartAccount/queries:loadByAuthUserId",
1697
- "org/lifecycle:load"
1698
- ]);
1699
- const OBSERVED_CONVEX_MUTATIONS = /* @__PURE__ */ new Set([
1700
- "movement/activity:annotate",
1701
- "financialOps/destinations:add",
1702
- "financialOps/destinations:remove",
1703
- "financialOps/addressBook:add",
1704
- "financialOps/addressBook:hide",
1705
- "financialOps/addressBook:unhide",
1706
- "financialOps/addressBook:label",
1707
- "financialOps/requestsInbox:issue",
1708
- "financialOps/requestsInbox:cancel",
1709
- "financialOps/requestsInbox:approve",
1710
- "financialOps/requestsInbox:decline",
1711
- "payroll/mutations:saveGroup",
1712
- "payroll/mutations:removeGroup",
1713
- "identity/mutations:create",
1714
- "identity/mutations:update",
1715
- "identity/mutations:completeOnboarding",
1716
- "smartAccount/mutations:provision",
1717
- "org/lifecycle:startOrResume",
1718
- "org/lifecycle:recordFailure",
1719
- "org/lifecycle:retry"
1720
- ]);
1721
- var ConvexCallAdapter = class {
1722
- #client;
1723
- #tokenProvider;
1724
- #applicationId;
1725
- #observation;
1726
- #connectionMonitor;
1727
- constructor(deps) {
1728
- this.#connectionMonitor = new ConvexConnectionMonitor();
1729
- const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
1730
- this.#client = deps.client ?? new ConvexClient(deps.convexUrl, webSocketConstructor === void 0 ? {} : { webSocketConstructor });
1731
- this.#tokenProvider = deps.tokenProvider;
1732
- this.#applicationId = deps.applicationId;
1733
- this.#observation = deps.observation;
1734
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
1735
- }
1736
- refreshAuth() {
1737
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
1738
- }
1739
- observeConnection(observer) {
1740
- return this.#connectionMonitor.observe(this.#client, observer);
1741
- }
1742
- query(fn, args) {
1743
- const path = getFunctionName(fn);
1744
- return Effect.serviceOption(Tracer.ParentSpan).pipe(Effect.flatMap((parent) => {
1745
- const traceparent = parent._tag === "Some" ? formatTraceparent(parent.value) : void 0;
1746
- return Effect.tryPromise({
1747
- try: () => this.#client.query(fn, this.#observedArgs(OBSERVED_CONVEX_QUERIES, path, args, traceparent)),
1748
- catch: (cause) => mapToConvexCallError(path, cause)
1749
- });
1750
- }));
1751
- }
1752
- mutation(fn, args) {
1753
- const path = getFunctionName(fn);
1754
- return Effect.serviceOption(Tracer.ParentSpan).pipe(Effect.flatMap((parent) => {
1755
- const traceparent = parent._tag === "Some" ? formatTraceparent(parent.value) : void 0;
1756
- return Effect.tryPromise({
1757
- try: () => this.#client.mutation(fn, this.#observedArgs(OBSERVED_CONVEX_MUTATIONS, path, args, traceparent)),
1758
- catch: (cause) => mapToConvexCallError(path, cause)
1759
- });
1760
- }));
1761
- }
1762
- action(fn, args) {
1763
- const path = getFunctionName(fn);
1764
- return Effect.serviceOption(Tracer.ParentSpan).pipe(Effect.flatMap((parent) => {
1765
- const traceparent = parent._tag === "Some" ? formatTraceparent(parent.value) : void 0;
1766
- const connectionAtStart = connectionState(this.#client);
1767
- return Effect.tryPromise({
1768
- try: () => this.#client.action(fn, this.#observedArgs(OBSERVED_CONVEX_ACTIONS, path, args, traceparent)),
1769
- catch: (cause) => {
1770
- const transport = this.#connectionMonitor.reconnectEvidence(connectionAtStart, connectionState(this.#client));
1771
- return mapToConvexCallError(path, cause, transport);
1772
- }
1773
- }).pipe(Effect.tapError((error) => logActionTransportFailure(path, error)));
1774
- }));
1775
- }
1776
- #observedArgs(allowlist, path, args, traceparent) {
1777
- if (!allowlist.has(path)) return args;
1778
- const carriedContext = sanitizeObservationContext(args.observationContext);
1779
- let hostContext;
1780
- const invocationSnapshot = readInvocationObservation(args);
1781
- if (invocationSnapshot !== void 0) hostContext = invocationSnapshot.active ? invocationSnapshot.context : void 0;
1782
- else {
1783
- const resolveContext = this.#observation?.resolveContext;
1784
- if (resolveContext !== void 0) try {
1785
- hostContext = resolveContext();
1786
- } catch {
1787
- hostContext = void 0;
1788
- }
1789
- }
1790
- if (hostContext === void 0 && carriedContext === void 0 && traceparent === void 0) return args;
1791
- hostContext = sanitizeObservationContext({
1792
- ...hostContext,
1793
- ...carriedContext,
1794
- ...this.#applicationId === void 0 ? {} : { applicationId: this.#applicationId },
1795
- ...traceparent === void 0 ? {} : { traceparent }
1796
- });
1797
- if (hostContext === void 0) return args;
1798
- return {
1799
- ...args,
1800
- observationContext: hostContext
1801
- };
1802
- }
1803
- subscribe(fn, args, callback) {
1804
- return Effect.try({
1805
- try: () => {
1806
- const path = getFunctionName(fn);
1807
- const unsubscribe = this.#client.onUpdate(fn, args, (value) => callback({
1808
- status: "ok",
1809
- value
1810
- }), (err) => callback({
1811
- status: "error",
1812
- error: mapToCapxulError(path, err)
1813
- }));
1814
- let active = true;
1815
- return () => {
1816
- if (!active) return;
1817
- active = false;
1818
- unsubscribe();
1819
- };
1820
- },
1821
- catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
1822
- }).pipe(Effect.tap(() => Effect.sync(() => {
1823
- callback({ status: "loading" });
1824
- })));
1825
- }
1826
- async close() {
1827
- this.#connectionMonitor.close();
1828
- await this.#client.close();
1829
- }
1830
- };
1831
- function ConvexCallLayer(deps) {
1832
- return Layer.effect(ConvexCallPortTag, Effect.acquireRelease(Effect.sync(() => new ConvexCallAdapter(deps)), (adapter) => Effect.promise(() => adapter.close()).pipe(Effect.orDie)));
1833
- }
1834
- function mapToCapxulError(operation, err, transport) {
1835
- const decoded = decodeConvexError(err);
1836
- if (decoded !== null) return decoded;
1837
- if (err instanceof CapxulError) return err;
1838
- if (transport !== void 0) {
1839
- const { kind: _kind, start_connection_count: _start, ...connection } = transport;
1840
- return Errors.networkError(operation, err, {
1841
- provider: "convex",
1842
- failure_mode: "upstream-down",
1843
- reason: "connection-lost-in-flight",
1844
- ...connection
1845
- });
1846
- }
1847
- if (err instanceof Error) {
1848
- if (isTransportError(err)) return Errors.networkError(operation, err, { provider: "convex" });
1849
- return Errors.providerError("convex", operation, err);
1850
- }
1851
- return Errors.providerError("convex", operation, new Error(String(err)));
1852
- }
1853
- function mapToConvexCallError(operation, err, transport) {
1854
- return convexCallErrorFromCapxul(operation, mapToCapxulError(operation, err, transport), transport);
1855
- }
1856
- function connectionState(client) {
1857
- try {
1858
- const state = client.connectionState?.();
1859
- return typeof state?.connectionCount === "number" ? state : void 0;
1860
- } catch {
1861
- return;
1862
- }
1863
- }
1864
- function logActionTransportFailure(operation, error) {
1865
- if (error.transport === void 0) return Effect.void;
1866
- const { kind, ...connection } = error.transport;
1867
- return Effect.logWarning("convex.action.connection_lost").pipe(Effect.annotateLogs({
1868
- operation,
1869
- failure_mode: "upstream-down",
1870
- reason: kind,
1871
- ...connection
1872
- }), Effect.catchCause(() => Effect.void));
1873
- }
1874
- const TRANSPORT_ERROR_CODES = /* @__PURE__ */ new Set([
1875
- "EAI_AGAIN",
1876
- "ECONNREFUSED",
1877
- "ECONNRESET",
1878
- "ENOTFOUND",
1879
- "EPIPE",
1880
- "ETIMEDOUT",
1881
- "UND_ERR_CONNECT_TIMEOUT",
1882
- "UND_ERR_HEADERS_TIMEOUT",
1883
- "UND_ERR_SOCKET"
1884
- ]);
1885
- function isTransportError(err) {
1886
- const seen = /* @__PURE__ */ new Set();
1887
- let current = err;
1888
- while (current !== void 0 && !seen.has(current)) {
1889
- seen.add(current);
1890
- const code = current.code;
1891
- if (current.name === "FetchError" || current.name === "NetworkError" || typeof code === "string" && TRANSPORT_ERROR_CODES.has(code)) return true;
1892
- current = current.cause instanceof Error ? current.cause : void 0;
1893
- }
1894
- return false;
1895
- }
1896
- //#endregion
1897
- //#region src/adapters/identity/ConvexIdentityAdapter.ts
1898
- const identityLoadByAuthUserIdQuery = makeFunctionReference(CAPXUL_FUNCTIONS["identity/queries"].loadByAuthUserId);
1899
- const identityCreateMutation = makeFunctionReference(CAPXUL_FUNCTIONS["identity/mutations"].create);
1900
- const identityUpdateMutation = makeFunctionReference(CAPXUL_FUNCTIONS["identity/mutations"].update);
1901
- const identityCompleteOnboardingMutation = makeFunctionReference(CAPXUL_FUNCTIONS["identity/mutations"].completeOnboarding);
1902
- var ConvexIdentityAdapter = class {
1903
- #convex;
1904
- constructor(deps) {
1905
- this.#convex = deps.convex;
1906
- }
1907
- loadByAuthUserId(authUserId) {
1908
- return this.#convex.query(identityLoadByAuthUserIdQuery, { authUserId }).pipe(Effect.mapError((error) => identityErrorFromCapxul("loadByAuthUserId", error.publicError, error)), Effect.flatMap((row) => Effect.try({
1909
- try: () => row === null ? null : brandProfile(row),
1910
- catch: (cause) => identityErrorFromUnknown("loadByAuthUserId", cause)
1911
- })), Effect.catchDefect((cause) => Effect.fail(identityErrorFromUnknown("loadByAuthUserId", cause))));
1912
- }
1913
- create(input) {
1914
- return this.#convex.mutation(identityCreateMutation, input).pipe(Effect.mapError((error) => identityErrorFromCapxul("create", error.publicError, error)), Effect.flatMap((row) => Effect.try({
1915
- try: () => brandProfile(row),
1916
- catch: (cause) => identityErrorFromUnknown("create", cause)
1917
- })), Effect.catchDefect((cause) => Effect.fail(identityErrorFromUnknown("create", cause))));
1918
- }
1919
- update(input) {
1920
- return this.#convex.mutation(identityUpdateMutation, input).pipe(Effect.mapError((error) => identityErrorFromCapxul("update", error.publicError, error)), Effect.flatMap((row) => Effect.try({
1921
- try: () => brandProfile(row),
1922
- catch: (cause) => identityErrorFromUnknown("update", cause)
1923
- })), Effect.catchDefect((cause) => Effect.fail(identityErrorFromUnknown("update", cause))));
1924
- }
1925
- completeOnboarding(input) {
1926
- return this.#convex.mutation(identityCompleteOnboardingMutation, input).pipe(Effect.mapError((error) => identityErrorFromCapxul("completeOnboarding", error.publicError, error)), Effect.flatMap((row) => Effect.try({
1927
- try: () => brandProfile(row),
1928
- catch: (cause) => identityErrorFromUnknown("completeOnboarding", cause)
1929
- })), Effect.catchDefect((cause) => Effect.fail(identityErrorFromUnknown("completeOnboarding", cause))));
1930
- }
1931
- };
1932
- function ConvexIdentityLayer() {
1933
- return Layer.effect(IdentityPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexIdentityAdapter({ convex })));
1934
- }
1935
- function brandProfile(raw) {
1936
- return {
1937
- authUserId: toAuthUserId(raw.authUserId),
1938
- ...raw.testerKind === void 0 ? {} : { testerKind: toTesterKind(raw.testerKind) },
1939
- email: toEmail(raw.email),
1940
- displayName: raw.displayName,
1941
- country: raw.country === null ? null : toCountryCode(raw.country),
1942
- onboarded: raw.onboarded ?? false,
1943
- withdrawalAddress: raw.withdrawalAddress === null || raw.withdrawalAddress === void 0 ? null : toAddress(raw.withdrawalAddress),
1944
- handle: raw.handle === null || raw.handle === void 0 ? null : toHandle(raw.handle),
1945
- imageUrl: raw.imageUrl ?? null,
1946
- kycTier: toKycTier(raw.kycTier),
1947
- createdAt: toEpochMs(raw.createdAt),
1948
- updatedAt: toEpochMs(raw.updatedAt)
1949
- };
1950
- }
1951
- function identityErrorFromUnknown(operation, cause) {
1952
- if (cause instanceof CapxulError) return identityErrorFromCapxul(operation, cause);
1953
- return identityErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
1954
- }
1955
- //#endregion
1956
- //#region src/adapters/account-read/ConvexAccountAdapter.ts
1957
- const DEFAULT_FUNCTIONS$3 = {
1958
- readBalance: makeFunctionReference(CAPXUL_FUNCTIONS["account/actions"].readBalance),
1959
- faucetMint: makeFunctionReference(CAPXUL_FUNCTIONS["account/actions"].faucetMint)
1960
- };
1961
- var ConvexAccountAdapter = class {
1962
- #convex;
1963
- #fns;
1964
- #telemetry;
1965
- constructor(deps) {
1966
- this.#convex = deps.convex;
1967
- this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$3;
1968
- this.#telemetry = deps.telemetry;
1969
- }
1970
- readBalance(input) {
1971
- return retryIdempotentRead(this.#convex.action(this.#fns.readBalance, copyInvocationObservation(input, { chainId: wireChainId(input.chainId) })), CAPXUL_OPERATIONS.accounts.read, this.#telemetry, input).pipe(Effect.mapError((error) => accountReadErrorFromCapxul("readBalance", error.publicError, error)), Effect.flatMap((wire) => brandAccountEffect("readBalance", wire)), Effect.catchDefect((cause) => Effect.fail(accountReadErrorFromUnknown("readBalance", cause))));
1972
- }
1973
- fundFromFaucet(input) {
1974
- const operation = "fundFromFaucet";
1975
- return Effect.suspend(() => {
1976
- const rawAmount = toWei(input.amount);
1977
- return this.#convex.action(this.#fns.faucetMint, {
1978
- chainId: wireChainId(input.chainId),
1979
- rawAmount
1980
- });
1981
- }).pipe(Effect.mapError((error) => accountReadErrorFromCapxul(operation, error.publicError, error)), Effect.map((wire) => ({ txHash: wire.txHash })), Effect.catchDefect((cause) => Effect.fail(accountReadErrorFromUnknown(operation, cause))));
1982
- }
1983
- };
1984
- function ConvexAccountLayer() {
1985
- return Layer.effect(AccountReadPortTag, Effect.all([ConvexCallPortTag, TelemetryPortTag]).pipe(Effect.map(([convex, telemetry]) => new ConvexAccountAdapter({
1986
- convex,
1987
- telemetry
1988
- }))));
1989
- }
1990
- function brandAccountEffect(operation, wire) {
1991
- return Effect.try({
1992
- try: () => brandAccount(wire),
1993
- catch: (cause) => accountReadErrorFromUnknown(operation, cause)
1994
- });
1995
- }
1996
- function brandAccount(wire) {
1997
- const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
1998
- const available = fromWei(wire.rawAvailableBalance ?? wire.rawBalance, wire.decimals, wire.currency);
1999
- return {
2000
- id: toAccountId(wire.accountId),
2001
- balance,
2002
- available
2003
- };
2004
- }
2005
- function accountReadErrorFromUnknown(operation, cause) {
2006
- if (cause instanceof CapxulError) return accountReadErrorFromCapxul(operation, cause);
2007
- return accountReadErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
2008
- }
2009
- //#endregion
2010
- //#region src/adapters/smart-account/ConvexSmartAccountAdapter.ts
2011
- const DEFAULT_FUNCTIONS$2 = {
2012
- loadByAuthUserId: makeFunctionReference(CAPXUL_FUNCTIONS["smartAccount/queries"].loadByAuthUserId),
2013
- loadBySmartAccountAddress: makeFunctionReference(CAPXUL_FUNCTIONS["smartAccount/queries"].loadBySmartAccountAddress),
2014
- provision: makeFunctionReference(CAPXUL_FUNCTIONS["smartAccount/mutations"].provision),
2015
- confirmDeployment: makeFunctionReference(CAPXUL_FUNCTIONS["smartAccount/actions"].confirmDeployment),
2016
- claim: makeFunctionReference(CAPXUL_FUNCTIONS["smartAccount/actions"].claim)
2017
- };
2018
- var ConvexSmartAccountAdapter = class {
2019
- #convex;
2020
- #fns;
2021
- constructor(deps) {
2022
- this.#convex = deps.convex;
2023
- this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$2;
2024
- }
2025
- loadByAuthUserId(authUserId) {
2026
- return this.#convex.query(this.#fns.loadByAuthUserId, { authUserId }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("loadByAuthUserId", error.publicError, error)), Effect.flatMap((row) => brandSmartAccountEffect("loadByAuthUserId", row)), Effect.catchDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("loadByAuthUserId", cause))));
2027
- }
2028
- loadBySmartAccountAddress(address) {
2029
- return this.#convex.query(this.#fns.loadBySmartAccountAddress, { address }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("loadBySmartAccountAddress", error.publicError, error)), Effect.flatMap((row) => brandSmartAccountEffect("loadBySmartAccountAddress", row)), Effect.catchDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("loadBySmartAccountAddress", cause))));
2030
- }
2031
- provision(input) {
2032
- return this.#convex.mutation(this.#fns.provision, { chainId: wireChainId(input.chainId) }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("provision", error.publicError, error)), Effect.flatMap((row) => Effect.try({
2033
- try: () => brandProvisionedSmartAccount(input.authUserId, row),
2034
- catch: (cause) => smartAccountErrorFromUnknown("provision", cause)
2035
- })), Effect.catchDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("provision", cause))));
2036
- }
2037
- confirmDeployment(input) {
2038
- const evidence = {
2039
- chainId: wireChainId(input.evidence.chainId),
2040
- signerAddress: input.evidence.signerAddress,
2041
- safeAddress: input.evidence.safeAddress,
2042
- ...input.evidence.userOpHash === void 0 ? {} : { userOpHash: input.evidence.userOpHash },
2043
- ...input.evidence.txHash === void 0 ? {} : { txHash: input.evidence.txHash },
2044
- ...input.evidence.blockNumber === void 0 ? {} : { blockNumber: input.evidence.blockNumber }
2045
- };
2046
- return this.#convex.action(this.#fns.confirmDeployment, {
2047
- chainId: wireChainId(input.chainId),
2048
- safeAddress: input.safeAddress,
2049
- evidence
2050
- }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("confirmDeployment", error.publicError, error)), Effect.flatMap((row) => Effect.try({
2051
- try: () => brandProvisionedSmartAccount(input.authUserId, row),
2052
- catch: (cause) => smartAccountErrorFromUnknown("confirmDeployment", cause)
2053
- })), Effect.catchDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("confirmDeployment", cause))));
2054
- }
2055
- claim(input) {
2056
- return this.#convex.action(this.#fns.claim, copyInvocationObservation(input, {
2057
- chainId: wireChainId(input.chainId),
2058
- signerAddress: input.signerAddress
2059
- })).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("claim", error.publicError, error)), Effect.flatMap((row) => Effect.try({
2060
- try: () => brandProvisionedSmartAccount(input.authUserId, row),
2061
- catch: (cause) => smartAccountErrorFromUnknown("claim", cause)
2062
- })), Effect.catchDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("claim", cause))));
2063
- }
2064
- };
2065
- function ConvexSmartAccountLayer() {
2066
- return Layer.effect(SmartAccountPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexSmartAccountAdapter({ convex })));
2067
- }
2068
- function brandSmartAccountEffect(operation, wire) {
2069
- return Effect.try({
2070
- try: () => wire === null ? null : brandNonNullSmartAccount(wire),
2071
- catch: (cause) => smartAccountErrorFromUnknown(operation, cause)
2072
- });
2073
- }
2074
- function brandNonNullSmartAccount(wire) {
2075
- return {
2076
- authUserId: toAuthUserId(wire.authUserId),
2077
- signerAddress: wire.signerAddress === null ? null : toAddress(wire.signerAddress),
2078
- smartAccountAddress: toAddress(wire.smartAccountAddress),
2079
- chainId: toChainId(wire.chainId),
2080
- deployedAt: wire.deployedAt === null ? null : toEpochMs(wire.deployedAt),
2081
- claimedAt: wire.claimedAt === null ? null : toEpochMs(wire.claimedAt),
2082
- createdAt: toEpochMs(wire.createdAt)
2083
- };
2084
- }
2085
- function brandProvisionedSmartAccount(requestedAuthUserId, wire) {
2086
- if (wire.authUserId !== String(requestedAuthUserId)) throw Errors.notAuthenticated();
2087
- return brandNonNullSmartAccount(wire);
2088
- }
2089
- function smartAccountErrorFromUnknown(operation, cause) {
2090
- if (cause instanceof CapxulError) return smartAccountErrorFromCapxul(operation, cause);
2091
- return smartAccountErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
2092
- }
2093
- //#endregion
2094
- //#region src/ports/org.ts
2095
- var OrgError = class extends Data.TaggedError("OrgError") {};
2096
- function orgErrorFromCapxul(operation, error, cause = error) {
2097
- return new OrgError({
2098
- operation,
2099
- publicCode: error.code,
2100
- publicError: error,
2101
- cause,
2102
- ...error.details === void 0 ? {} : { details: error.details }
2103
- });
2104
- }
2105
- var OrgPortTag = class extends Context.Service()("@capxul/sdk/ports/OrgPort") {};
2106
- //#endregion
2107
- //#region src/adapters/org/parse.ts
2108
- /**
2109
- * Map a `WireOrg` + its (separately read) treasury `Account` into the branded
2110
- * `OrgView`. The viewer role is projected by the authenticated backend read;
2111
- * it must never be inferred from Organization ownership. Brands at the read
2112
- * edge: `orgId`, `safeAddress`.
2113
- */
2114
- function brandOrgView(wire, treasury, viewerRole) {
2115
- return {
2116
- id: toOrgId(wire.orgId),
2117
- ...wire.creationSource === void 0 ? {} : { creationSource: toTesterKind(wire.creationSource) },
2118
- name: wire.name,
2119
- handle: wire.handle,
2120
- safeAddress: toAddress(wire.safeAddress.toLowerCase()),
2121
- role: viewerRole,
2122
- treasury,
2123
- bio: wire.bio ?? null,
2124
- size: wire.size ?? null,
2125
- logoUrl: wire.logoUrl ?? null
2126
- };
2127
- }
2128
- /**
2129
- * Build the Org treasury `Account` from the raw on-chain `balanceOf` integer
2130
- * (the D3 RPC read). `available === balance` for a treasury with no envelope
2131
- * partition yet (a fresh Org reads back $0).
2132
- */
2133
- function brandOrgTreasury(input) {
2134
- return {
2135
- id: toAccountId(`account_${orgIdBody(input.orgId)}`),
2136
- balance: input.money,
2137
- available: input.money
2138
- };
2139
- }
2140
- function brandOrgRole(wire) {
2141
- return {
2142
- orgId: toOrgId(wire.orgId),
2143
- label: wire.label,
2144
- roleKey: toRoleKey(wire.roleKey),
2145
- definition: parseRoleDefinition(wire.definitionJson)
2146
- };
2147
- }
2148
- function brandOrgMember(wire) {
2149
- return {
2150
- orgId: toOrgId(wire.orgId),
2151
- email: toEmail(wire.email),
2152
- name: wire.name,
2153
- personalSafeAddress: wire.personalSafeAddress === null ? null : toAddress(wire.personalSafeAddress),
2154
- role: wire.role,
2155
- roleKey: wire.roleKey === null ? null : toRoleKey(wire.roleKey),
2156
- status: wire.status,
2157
- grantTxHash: wire.grantTxHash,
2158
- revokeTxHash: wire.revokeTxHash
2159
- };
2160
- }
2161
- function parseRoleDefinition(json) {
2162
- let raw;
2163
- try {
2164
- const parsed = JSON.parse(json);
2165
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("expected object");
2166
- raw = parsed;
2167
- } catch (err) {
2168
- throw new Error(`Invalid role definition: malformed JSON - ${err instanceof Error ? err.message : String(err)}`, { cause: err });
2169
- }
2170
- if (typeof raw.label !== "string" || raw.label.trim().length === 0) throw new Error("Invalid role definition: missing label");
2171
- return {
2172
- label: raw.label,
2173
- ...raw.spend === void 0 ? {} : { spend: {
2174
- ...raw.spend.perTx === void 0 ? {} : { perTx: parseRoleMoney(raw.spend.perTx, "perTx") },
2175
- ...raw.spend.perDay === void 0 ? {} : { perDay: parseRoleMoney(raw.spend.perDay, "perDay") },
2176
- ...raw.spend.toRecipients === void 0 ? {} : { toRecipients: parseRoleRecipients(raw.spend.toRecipients) }
2177
- } },
2178
- ...typeof raw.canSpend === "boolean" ? { canSpend: raw.canSpend } : {},
2179
- ...typeof raw.canManageMembers === "boolean" ? { canManageMembers: raw.canManageMembers } : {},
2180
- ...typeof raw.canManageRoles === "boolean" ? { canManageRoles: raw.canManageRoles } : {}
2181
- };
2182
- }
2183
- function parseRoleMoney(raw, field) {
2184
- if (typeof raw.currency !== "string" || typeof raw.value !== "string" || !/^\d+$/.test(raw.value) || raw.decimals !== 6) throw new Error(`Invalid role money: ${field}`);
2185
- return {
2186
- currency: toCurrencyCode(raw.currency),
2187
- value: raw.value,
2188
- decimals: raw.decimals
2189
- };
2190
- }
2191
- function parseRoleRecipients(raw) {
2192
- if (raw === "anyone") return "anyone";
2193
- if (!Array.isArray(raw)) throw new Error("Invalid role definition: toRecipients must be \"anyone\" or an array");
2194
- return raw.map((recipient) => {
2195
- if (typeof recipient !== "string") throw new Error("Invalid role definition: toRecipients must be \"anyone\" or an array");
2196
- return toAddress(recipient);
2197
- });
2198
- }
2199
- /** Strip the `org_` prefix + non-alphanumerics so the body re-seeds `account_`. */
2200
- function orgIdBody(orgId) {
2201
- const underscore = orgId.indexOf("_");
2202
- const cleaned = (underscore < 0 ? orgId : orgId.slice(underscore + 1)).replace(/[^0-9A-Za-z]/g, "");
2203
- return cleaned.length > 0 ? cleaned : "0";
2204
- }
2205
- //#endregion
2206
- //#region src/adapters/org/ConvexOrganizationAdapter.ts
2207
- const DEFAULT_FUNCTIONS$1 = {
2208
- listAll: makeFunctionReference(CAPXUL_FUNCTIONS["org/queries"].listAll),
2209
- listRoles: makeFunctionReference(CAPXUL_FUNCTIONS["org/queries"].listRolesByOrgId),
2210
- listMembers: makeFunctionReference(CAPXUL_FUNCTIONS["org/queries"].listMembersByOrgId),
2211
- readTreasury: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].readTreasury),
2212
- inviteMember: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].inviteMember),
2213
- resendInvite: makeFunctionReference(CAPXUL_FUNCTIONS["org/mutations"].resendInviteToken),
2214
- detectInvitations: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].detectAndAcceptPendingInvitations)
2215
- };
2216
- /**
2217
- * Standard production Organization read adapter. It intentionally has no
2218
- * deployer/RPC/test configuration: authenticated Convex actions own live chain
2219
- * reads, while the lifecycle adapter owns the single sponsored bootstrap.
2220
- */
2221
- var ConvexOrganizationAdapter = class {
2222
- #convex;
2223
- #fns;
2224
- #telemetry;
2225
- constructor(input) {
2226
- this.#convex = input.convex;
2227
- this.#fns = input.functions ?? DEFAULT_FUNCTIONS$1;
2228
- this.#telemetry = input.telemetry;
2229
- }
2230
- createOrg(_input) {
2231
- return Effect.fail(orgErrorFromCapxul("createOrg", Errors.notImplemented("organizationSetup", "use onboarding.completeOrganization")));
2232
- }
2233
- listOrgs(input) {
2234
- return this.#convex.query(this.#fns.listAll, copyInvocationObservation(input, {})).pipe(Effect.mapError((error) => orgErrorFromCapxul("listOrgs", error.publicError, error)), Effect.flatMap((wires) => Effect.forEach(wires, (wire) => this.#readTreasuryWire(copyInvocationObservation(input, { orgId: toOrgId(wire.orgId) })).pipe(Effect.map((treasury) => {
2235
- const viewerRole = wire.viewerRole.trim();
2236
- if (viewerRole.length === 0) throw Errors.wrongState({
2237
- method: "listOrgs",
2238
- currentState: "viewerRoleMissing",
2239
- validStates: ["activeViewerRole"]
2240
- });
2241
- return brandOrgView(wire, treasury, viewerRole);
2242
- })))), Effect.catchDefect((cause) => Effect.fail(toOrgError("listOrgs", cause))));
2243
- }
2244
- readTreasury(input) {
2245
- return this.#readTreasuryWire(input).pipe(Effect.catchDefect((cause) => Effect.fail(toOrgError("readTreasury", cause))));
2246
- }
2247
- listRoles(input) {
2248
- return this.#convex.query(this.#fns.listRoles, { orgId: input.orgId }).pipe(Effect.mapError((error) => orgErrorFromCapxul("listRoles", error.publicError, error)), Effect.flatMap((rows) => rows.length === 0 ? Effect.fail(partialOrgTruth("listRoles", "activeRoleMissing")) : Effect.succeed(rows.map(brandOrgRole))), Effect.catchDefect((cause) => Effect.fail(toOrgError("listRoles", cause))));
2249
- }
2250
- listMembers(input) {
2251
- return this.#convex.query(this.#fns.listMembers, { orgId: input.orgId }).pipe(Effect.mapError((error) => orgErrorFromCapxul("listMembers", error.publicError, error)), Effect.flatMap((rows) => rows.length === 0 ? Effect.fail(partialOrgTruth("listMembers", "activeMemberMissing")) : Effect.succeed(rows.map(brandOrgMember))), Effect.catchDefect((cause) => Effect.fail(toOrgError("listMembers", cause))));
2252
- }
2253
- pendingMembers(input) {
2254
- return this.listMembers(input).pipe(Effect.map((members) => members.filter((member) => member.status === "pending" || member.status === "pending_safe" || member.status === "pending_grant")));
2255
- }
2256
- inviteMember(input) {
2257
- return this.#convex.action(this.#fns.inviteMember, {
2258
- orgId: input.orgId,
2259
- email: input.input.email,
2260
- role: input.input.role
2261
- }).pipe(Effect.mapError((error) => orgErrorFromCapxul("inviteMember", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchDefect((cause) => Effect.fail(toOrgError("inviteMember", cause))));
2262
- }
2263
- resendInviteToken(input) {
2264
- return this.#convex.mutation(this.#fns.resendInvite, {
2265
- orgId: input.orgId,
2266
- email: input.email
2267
- }).pipe(Effect.mapError((error) => orgErrorFromCapxul("resendInviteToken", error.publicError, error)), Effect.map(brandOrgMember), Effect.catchDefect((cause) => Effect.fail(toOrgError("resendInviteToken", cause))));
2268
- }
2269
- detectAndAcceptPendingInvitations(_input) {
2270
- return this.#convex.action(this.#fns.detectInvitations, {}).pipe(Effect.mapError((error) => orgErrorFromCapxul("detectAndAcceptPendingInvitations", error.publicError, error)), Effect.map((result) => ({ matched: result.matched.map(toOrgId) })), Effect.catchDefect((cause) => Effect.fail(toOrgError("detectAndAcceptPendingInvitations", cause))));
2271
- }
2272
- #readTreasuryWire(input) {
2273
- const orgId = String(input.orgId);
2274
- return retryIdempotentRead(this.#convex.action(this.#fns.readTreasury, copyInvocationObservation(input, { orgId })), CAPXUL_OPERATIONS.org.treasury, this.#telemetry, input).pipe(Effect.mapError((error) => orgErrorFromCapxul("readTreasury", error.publicError, error)), Effect.map((wire) => {
2275
- if (wire.orgId !== orgId) throw Errors.invalidInput("orgId", "Organization treasury scope does not match");
2276
- const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
2277
- const available = fromWei(wire.rawAvailableBalance, wire.decimals, wire.currency);
2278
- return {
2279
- ...brandOrgTreasury({
2280
- orgId: wire.orgId,
2281
- money: balance
2282
- }),
2283
- available
2284
- };
2285
- }));
2286
- }
2287
- };
2288
- function toOrgError(operation, cause) {
2289
- if (cause instanceof CapxulError) return orgErrorFromCapxul(operation, cause);
2290
- return orgErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
2291
- }
2292
- function partialOrgTruth(operation, currentState) {
2293
- return orgErrorFromCapxul(operation, Errors.wrongState({
2294
- method: operation,
2295
- currentState,
2296
- validStates: ["completeProductionOrganizationTruth"]
2297
- }));
2298
- }
2299
- //#endregion
2300
- //#region src/adapters/org/ConvexOrganizationSetupAdapter.ts
2301
- const DEFAULT_FUNCTIONS = {
2302
- startOrResume: makeFunctionReference(CAPXUL_FUNCTIONS["org/lifecycle"].startOrResume),
2303
- prepareFounderAccount: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].prepareFounderAccount),
2304
- prepareBootstrap: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].prepareBootstrap),
2305
- submitBootstrap: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].submitBootstrap),
2306
- resumeBootstrapSubmission: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].resumeBootstrapSubmission),
2307
- confirmBootstrap: makeFunctionReference(CAPXUL_FUNCTIONS["org/actions"].confirmBootstrap),
2308
- recordFailure: makeFunctionReference(CAPXUL_FUNCTIONS["org/lifecycle"].recordFailure),
2309
- load: makeFunctionReference(CAPXUL_FUNCTIONS["org/lifecycle"].load),
2310
- retry: makeFunctionReference(CAPXUL_FUNCTIONS["org/lifecycle"].retry)
2311
- };
2312
- /** Durable Organization setup capability composed by the standard client. */
2313
- var ConvexOrganizationSetupAdapter = class {
2314
- #convex;
2315
- #signer;
2316
- #chainId;
2317
- #fns;
2318
- constructor(input) {
2319
- this.#convex = input.convex;
2320
- this.#signer = input.signer;
2321
- this.#chainId = input.chainId;
2322
- this.#fns = input.functions ?? DEFAULT_FUNCTIONS;
2323
- }
2324
- async startOrResume(input) {
2325
- const result = await runCall("startOrResume", this.#convex.mutation(this.#fns.startOrResume, copyInvocationObservation(input, {
2326
- ...input,
2327
- chainId: this.#chainId
2328
- })));
2329
- if (!result.ok) return result;
2330
- const lifecycle = parseLifecycle("startOrResume", result.value.lifecycle);
2331
- if (!lifecycle.ok) return lifecycle;
2332
- const orgId = parseOrgId("startOrResume", result.value.orgId);
2333
- if (!orgId.ok) return orgId;
2334
- if (String(orgId.value) !== String(lifecycle.value.orgId)) return fail(Errors.invalidInput("orgId", "Organization lifecycle scope does not match"));
2335
- return {
2336
- ok: true,
2337
- value: {
2338
- orgId: orgId.value,
2339
- lifecycle: lifecycle.value
2340
- }
2341
- };
2342
- }
2343
- prepareFounderAccount(input) {
2344
- return this.#lifecycleAction("prepareFounderAccount", input, () => this.#convex.action(this.#fns.prepareFounderAccount, copyInvocationObservation(input, {
2345
- orgId: input.orgId,
2346
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2347
- })));
2348
- }
2349
- async authorizeAndSubmitBootstrap(input) {
2350
- const cancelled = cancellation(input.signal);
2351
- if (cancelled !== void 0) return cancelled;
2352
- const signerAddress = await signerResult(this.#signer.source, "getAddress", () => this.#signer.getAddress());
2353
- if (!signerAddress.ok) return signerAddress;
2354
- const prepared = await runCall("prepareBootstrap", this.#convex.action(this.#fns.prepareBootstrap, copyInvocationObservation(input, {
2355
- orgId: input.orgId,
2356
- signerAddress: signerAddress.value,
2357
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2358
- })));
2359
- if (!prepared.ok) return prepared;
2360
- const authority = validatePreparedAuthorities(prepared.value, signerAddress.value);
2361
- if (!authority.ok) return authority;
2362
- const cancelledAfterPrepare = cancellation(input.signal);
2363
- if (cancelledAfterPrepare !== void 0) return cancelledAfterPrepare;
2364
- const signature = await signerResult(this.#signer.source, "signUserOpHash", () => this.#signer.signUserOpHash(prepared.value.digest));
2365
- if (!signature.ok) return signature;
2366
- const cancelledAfterSign = cancellation(input.signal);
2367
- if (cancelledAfterSign !== void 0) return cancelledAfterSign;
2368
- const submitted = await runCall("submitBootstrap", this.#convex.action(this.#fns.submitBootstrap, copyInvocationObservation(input, {
2369
- orgId: input.orgId,
2370
- signerAddress: signerAddress.value,
2371
- signature: signature.value,
2372
- userOp: prepared.value.userOp,
2373
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2374
- })));
2375
- if (!submitted.ok) return submitted;
2376
- return parseLifecycle("submitBootstrap", submitted.value);
2377
- }
2378
- resumeSubmittedBootstrap(input) {
2379
- return this.#lifecycleAction("resumeBootstrapSubmission", input, () => this.#convex.action(this.#fns.resumeBootstrapSubmission, copyInvocationObservation(input, {
2380
- orgId: input.orgId,
2381
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2382
- })));
2383
- }
2384
- confirmSubmittedBootstrap(input) {
2385
- return this.#lifecycleAction("confirmBootstrap", input, () => this.#convex.action(this.#fns.confirmBootstrap, copyInvocationObservation(input, {
2386
- orgId: input.orgId,
2387
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext },
2388
- ...input.retryDelayMs === void 0 ? {} : { retryDelayMs: input.retryDelayMs }
2389
- })));
2390
- }
2391
- async recordFailure(input) {
2392
- const errorProvider = input.error.details?.provider;
2393
- const errorOperation = input.error.details?.operation;
2394
- const result = await runCall("recordFailure", this.#convex.mutation(this.#fns.recordFailure, copyInvocationObservation(input, {
2395
- orgId: input.orgId,
2396
- errorCode: input.error.code,
2397
- ...typeof errorProvider === "string" && typeof errorOperation === "string" ? {
2398
- errorProvider,
2399
- errorOperation
2400
- } : {},
2401
- retryable: input.retryable,
2402
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2403
- })));
2404
- return result.ok ? parseLifecycle("recordFailure", result.value) : result;
2405
- }
2406
- async loadLifecycle(input) {
2407
- const result = await runCall("loadLifecycle", this.#convex.query(this.#fns.load, input));
2408
- if (!result.ok) return result;
2409
- if (result.value === null) return fail(Errors.invalidInput("orgId", "Organization lifecycle was not found"));
2410
- return parseLifecycle("loadLifecycle", result.value);
2411
- }
2412
- async retry(input) {
2413
- const cancelled = cancellation(input.signal);
2414
- if (cancelled !== void 0) return cancelled;
2415
- const current = await this.loadLifecycle({
2416
- orgId: input.orgId,
2417
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2418
- });
2419
- if (!current.ok) return current;
2420
- if (current.value.status === "failed" && current.value.retryable && (current.value.at === "awaitingFounderAuthorization" || current.value.at === "submittingBootstrap")) {
2421
- const reset = resetSignerSession(this.#signer);
2422
- if (!reset.ok) return reset;
2423
- }
2424
- const result = await runCall("retry", this.#convex.mutation(this.#fns.retry, copyInvocationObservation(input, {
2425
- orgId: input.orgId,
2426
- ...input.observationContext === void 0 ? {} : { observationContext: input.observationContext }
2427
- })));
2428
- if (!result.ok) return result;
2429
- return parseLifecycle("retry", result.value);
2430
- }
2431
- async #lifecycleAction(operation, input, call) {
2432
- const cancelled = cancellation(input.signal);
2433
- if (cancelled !== void 0) return cancelled;
2434
- const result = await runCall(operation, call());
2435
- if (!result.ok) return result;
2436
- const cancelledAfter = cancellation(input.signal);
2437
- if (cancelledAfter !== void 0) return cancelledAfter;
2438
- return parseLifecycle(operation, result.value);
2439
- }
2440
- };
2441
- function resetSignerSession(signer) {
2442
- const resetSession = signer.resetSession;
2443
- if (typeof resetSession !== "function") return {
2444
- ok: true,
2445
- value: void 0
2446
- };
2447
- try {
2448
- resetSession.call(signer);
2449
- return {
2450
- ok: true,
2451
- value: void 0
2452
- };
2453
- } catch (cause) {
2454
- return fail(signerFailure(signer.source, "resetSession", cause));
2455
- }
2456
- }
2457
- async function runCall(operation, effect) {
2458
- try {
2459
- const result = await Effect.runPromise(Effect.result(effect));
2460
- return Result.isSuccess(result) ? {
2461
- ok: true,
2462
- value: result.success
2463
- } : fail(publicError(operation, result.failure));
2464
- } catch (cause) {
2465
- return fail(publicError(operation, cause));
2466
- }
2467
- }
2468
- function publicError(operation, cause) {
2469
- if (cause instanceof CapxulError) return cause;
2470
- if (typeof cause === "object" && cause !== null) {
2471
- const carried = cause.publicError;
2472
- if (carried instanceof CapxulError) return carried;
2473
- }
2474
- return Errors.providerError("convex-organization", operation, cause);
2475
- }
2476
- async function signerResult(source, operation, run) {
2477
- try {
2478
- return {
2479
- ok: true,
2480
- value: await run()
2481
- };
2482
- } catch (cause) {
2483
- return fail(signerFailure(source, operation, cause));
2484
- }
2485
- }
2486
- function validatePreparedAuthorities(prepared, signerAddress) {
2487
- const signer = signerAddress.toLowerCase();
2488
- const preparedSigner = prepared.signerAddress.toLowerCase();
2489
- const founder = prepared.founderPersonalAccount.toLowerCase();
2490
- const organization = prepared.organizationAccountAddress.toLowerCase();
2491
- const sender = prepared.userOp.sender.toLowerCase();
2492
- if (preparedSigner !== signer) return fail(Errors.invalidInput("signerAddress", "Prepared signer does not match configured signer"));
2493
- if (founder === signer || organization === signer || organization === founder) return fail(Errors.invalidInput("organizationAuthority", "Signer EOA, founder Account, and Organization Account must be distinct"));
2494
- if (sender !== founder) return fail(Errors.invalidInput("userOp.sender", "Bootstrap sender must be the founder Account"));
2495
- if (!/^0x[0-9a-fA-F]{64}$/u.test(prepared.digest)) return fail(Errors.invalidInput("digest", "Prepared bootstrap digest must be 32-byte hex"));
2496
- return {
2497
- ok: true,
2498
- value: void 0
2499
- };
2500
- }
2501
- function parseLifecycle(operation, wire) {
2502
- const orgId = parseOrgId(operation, wire.orgId);
2503
- if (!orgId.ok) return orgId;
2504
- if (wire.status === "loading") return {
2505
- ok: true,
2506
- value: {
2507
- status: "loading",
2508
- orgId: orgId.value
2509
- }
2510
- };
2511
- if (wire.status === "ready") return {
2512
- ok: true,
2513
- value: {
2514
- status: "ready",
2515
- orgId: orgId.value,
2516
- canTransact: true
2517
- }
2518
- };
2519
- if (wire.status === "failed") {
2520
- if (!isSetupStep(wire.at)) return fail(Errors.invalidInput("lifecycle.at", "Unknown setup step"));
2521
- return {
2522
- ok: true,
2523
- value: {
2524
- status: "failed",
2525
- orgId: orgId.value,
2526
- at: wire.at,
2527
- error: new CapxulError(wire.error.code, wire.error.message),
2528
- retryable: wire.retryable
2529
- }
2530
- };
2531
- }
2532
- if (!isSetupStep(wire.step)) return fail(Errors.invalidInput("lifecycle.step", `Unknown setup step from ${operation}`));
2533
- return {
2534
- ok: true,
2535
- value: {
2536
- status: "settingUp",
2537
- orgId: orgId.value,
2538
- step: wire.step
2539
- }
2540
- };
2541
- }
2542
- function parseOrgId(operation, value) {
2543
- try {
2544
- return {
2545
- ok: true,
2546
- value: toOrgId(value)
2547
- };
2548
- } catch (cause) {
2549
- return fail(Errors.providerError("convex-organization", operation, cause));
2550
- }
2551
- }
2552
- function isSetupStep(value) {
2553
- return value === "preparingFounderAccount" || value === "awaitingFounderAuthorization" || value === "submittingBootstrap" || value === "confirmingBootstrap";
2554
- }
2555
- function cancellation(signal) {
2556
- return signal?.aborted ? fail(Errors.cancelled({ operation: CAPXUL_OPERATIONS.organization.setup })) : void 0;
2557
- }
2558
- function fail(error) {
2559
- return {
2560
- ok: false,
2561
- error
2562
- };
2563
- }
2564
- //#endregion
2565
- //#region src/adapters/telemetry/PostHogTelemetryAdapter.ts
2566
- var PostHogTelemetryAdapter = class {
2567
- #capture;
2568
- #identify;
2569
- #group;
2570
- #reset;
2571
- constructor(deps) {
2572
- this.#capture = deps.capture;
2573
- this.#identify = deps.identify ?? (() => void 0);
2574
- this.#group = deps.group ?? (() => void 0);
2575
- this.#reset = deps.reset ?? (() => void 0);
2576
- }
2577
- emit(event) {
2578
- return this.#run(() => this.#capture(event.name, redactTelemetryProps(event.name, cloneProps(event.props)), event), "emit", event.name);
2579
- }
2580
- identify(input) {
2581
- return this.#run(() => this.#identify(cloneIdentifyInput(input)), "identify");
2582
- }
2583
- group(input) {
2584
- return this.#run(() => this.#group(cloneGroupInput(input)), "group");
2585
- }
2586
- reset() {
2587
- return this.#run(() => this.#reset(), "reset");
2588
- }
2589
- #run(operation, operationName, eventName) {
2590
- const diagnose = Effect.logWarning("product.telemetry.transport.dropped").pipe(Effect.annotateLogs({
2591
- operation: operationName,
2592
- ...eventName === void 0 ? {} : { product_event: eventName }
2593
- }), Effect.catchCause(() => Effect.void));
2594
- return Effect.suspend(() => {
2595
- let pending;
2596
- try {
2597
- pending = operation();
2598
- } catch {
2599
- return diagnose;
2600
- }
2601
- if (pending === void 0) return Effect.void;
2602
- const transport = Effect.tryPromise({
2603
- try: () => pending,
2604
- catch: () => void 0
2605
- }).pipe(Effect.catch(() => diagnose));
2606
- return Effect.forkDetach(transport, { startImmediately: true }).pipe(Effect.asVoid);
2607
- });
2608
- }
2609
- };
2610
- function PostHogTelemetryLayer(deps) {
2611
- return Layer.succeed(TelemetryPortTag, new PostHogTelemetryAdapter(deps));
2612
- }
2613
- function cloneIdentifyInput(input) {
2614
- const traits = input.traits === void 0 ? void 0 : cloneProps(input.traits);
2615
- const properties = input.properties === void 0 ? void 0 : cloneProps(input.properties);
2616
- return {
2617
- distinctId: input.distinctId,
2618
- ...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
2619
- ...traits === void 0 ? {} : { traits },
2620
- ...properties === void 0 ? {} : { properties }
2621
- };
2622
- }
2623
- function cloneGroupInput(input) {
2624
- const properties = input.properties === void 0 ? void 0 : cloneProps(input.properties);
2625
- return properties === void 0 ? {
2626
- groupType: input.groupType,
2627
- groupKey: input.groupKey
2628
- } : {
2629
- groupType: input.groupType,
2630
- groupKey: input.groupKey,
2631
- properties
2632
- };
2633
- }
2634
- function cloneProps(props) {
2635
- if (props === void 0) return void 0;
2636
- const cloned = {};
2637
- for (const [key, value] of Object.entries(props)) cloned[key] = cloneTelemetryValue(value);
2638
- return cloned;
2639
- }
2640
- function cloneTelemetryValue(value) {
2641
- if (Array.isArray(value)) return value.map(cloneTelemetryValue);
2642
- if (value === null || typeof value !== "object") return value;
2643
- if (Object.getPrototypeOf(value) !== Object.prototype) return value;
2644
- const cloned = {};
2645
- for (const [key, nested] of Object.entries(value)) cloned[key] = cloneTelemetryValue(nested);
2646
- return cloned;
2647
- }
2648
- //#endregion
2649
- //#region src/adapters/diagnostic/ConsoleDiagnosticAdapter.ts
2650
- const DEFAULT_ACCOUNT_SETUP_LOG_PREFIX = "[capxul:account-setup]";
2651
- var ConsoleDiagnosticAdapter = class {
2652
- prefix;
2653
- constructor(prefix = DEFAULT_ACCOUNT_SETUP_LOG_PREFIX) {
2654
- this.prefix = prefix;
2655
- }
2656
- trace(scope, detail) {
2657
- globalThis.console?.debug?.(`${this.prefix} ${scope}`, detail);
2658
- }
2659
- };
2660
- //#endregion
2661
- //#region src/openfort/create-openfort-browser-signer.ts
2662
- function httpStatusOf(link) {
2663
- const carrier = link;
2664
- if (typeof carrier.response?.status === "number") return carrier.response.status;
2665
- if (typeof carrier.statusCode === "number") return carrier.statusCode;
2666
- if (typeof carrier.status === "number") return carrier.status;
2667
- }
2668
- function isUnauthorizedCause(cause) {
2669
- for (const link of causeChain(cause)) if (httpStatusOf(link) === 401) return true;
2670
- return false;
2671
- }
2672
- function openfortProviderError(operation, cause) {
2673
- if (cause instanceof CapxulError) return cause;
2674
- const failure_mode = operation === "configure" && isUnauthorizedCause(cause) ? "unauthorized" : "unknown";
2675
- return Errors.providerError("openfort", operation, cause, { failure_mode });
2676
- }
2677
- function openfortFailureMode(error) {
2678
- return error.mode ?? "unknown";
2679
- }
2680
- /** Build a named PROVIDER_ERROR. The assembled Core SDK boundary reports it. */
2681
- function failOpenfort(operation, failure_mode, cause) {
2682
- return Errors.providerError("openfort", operation, cause, { failure_mode });
2683
- }
2684
- /**
2685
- * True when the browser cannot perform Web Crypto — sandboxed iframes, headless
2686
- * agent browsers, or non-HTTPS origins. OpenFort's embedded-wallet `configure`
2687
- * silently produces no address in this state, so we detect it up front and name
2688
- * it `no-secure-context` instead of letting it decay into `unknown`.
2689
- */
2690
- function isInsecureBrowserContext() {
2691
- return globalThis.isSecureContext === false || globalThis.crypto?.subtle === void 0;
2692
- }
2693
- /** Openfort SDK storage keys (`@openfort/openfort-js` StorageKeys). */
2694
- const OPENFORT_BROWSER_STORAGE_KEYS = [
2695
- "openfort.authentication",
2696
- "openfort.account",
2697
- "openfort.session",
2698
- "openfort.configuration"
2699
- ];
2700
- /**
2701
- * Matches `@openfort/openfort-js` ScopedStorage.createScope — chars 8–15 of the
2702
- * publishable key, prefixed onto each StorageKeys entry in localStorage.
2703
- */
2704
- function openfortBrowserStorageScope(publishableKey) {
2705
- const trimmed = publishableKey.trim();
2706
- if (trimmed.length < 16) return;
2707
- return trimmed.substring(8, 16);
2708
- }
2709
- /**
2710
- * Drop cached Openfort auth/account state so third-party login re-runs for the
2711
- * current Better Auth session. The SDK skips `authenticateThirdParty` when a
2712
- * stale `userId` is already in storage, which yields 401 on `v2/accounts`.
2713
- */
2714
- function clearStaleOpenfortBrowserStorage(publishableKey) {
2715
- if (typeof localStorage === "undefined") return;
2716
- const scope = openfortBrowserStorageScope(publishableKey);
2717
- if (scope === void 0) return;
2718
- for (const key of OPENFORT_BROWSER_STORAGE_KEYS) localStorage.removeItem(`${scope}.${key}`);
2719
- }
2720
- /**
2721
- * The Openfort error code that names the stale-user class (#1435).
2722
- * `getThirdPartyAuthToken` skips `authenticateThirdParty` while a `userId` sits
2723
- * in scoped storage. A purged `userId` therefore pins every later call to a 401
2724
- * that `extractApiError` reports as `USER_NOT_FOUND`.
2725
- *
2726
- * The set holds one member on purpose. Session-expiry codes (`SESSION_EXPIRED`,
2727
- * `NOT_LOGGED_IN`, `INVALID_TOKEN`, `REFRESH_TOKEN_ERROR`) are a different
2728
- * cause, and a bare 401 is a different cause again: `app-env-allowlist` is a
2729
- * 401 by definition (`packages/errors/src/errors.ts:80-81`). Healing those and
2730
- * tagging them `stale-openfort-cache` would delete the triage signal the tag
2731
- * exists to carry.
2732
- */
2733
- const STALE_USER_ERROR_CODES = /* @__PURE__ */ new Set(["USER_NOT_FOUND"]);
2734
- /**
2735
- * The one predicate that opens the heal. It reads the Openfort error code and
2736
- * never the message text (ADR-0023 R4). It walks the cause chain the way
2737
- * `isTransportError` walks it in the Convex transport adapter.
2738
- *
2739
- * Known ceiling (#1435): a 401 that carries no recognized code is NOT healed.
2740
- * `extractApiError` keeps the status only on `AuthenticationError`, so such a
2741
- * payload is reachable. Add the code a live payload shows; do not add a message
2742
- * match, because that trades one bug class for an ADR-0023 R4 violation.
2743
- */
2744
- function isStaleUserSignal(cause) {
2745
- for (const link of causeChain(cause)) {
2746
- const error = link;
2747
- if (typeof error.error === "string" && STALE_USER_ERROR_CODES.has(error.error) || typeof error.code === "string" && STALE_USER_ERROR_CODES.has(error.code)) return true;
2748
- }
2749
- return false;
2750
- }
2751
- function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
2752
- const diagnostic = options.diagnostic;
2753
- const authBaseUrl = normalizeBetterAuthBaseUrl(bootstrap.authBaseUrl);
2754
- let currentStatus = "unknown";
2755
- const statusListeners = /* @__PURE__ */ new Set();
2756
- function setStatus(next) {
2757
- if (currentStatus === next) return;
2758
- currentStatus = next;
2759
- for (const listener of statusListeners) try {
2760
- listener(next);
2761
- } catch {
2762
- statusListeners.delete(listener);
2763
- diagnostic?.trace("openfort.signerStatusListener", {
2764
- ok: false,
2765
- failure_mode: "unknown"
2766
- });
2767
- }
2768
- }
2769
- const statusStore = {
2770
- status: () => currentStatus,
2771
- subscribe: (listener) => {
2772
- statusListeners.add(listener);
2773
- return () => {
2774
- statusListeners.delete(listener);
2775
- };
2776
- }
2777
- };
2778
- /**
2779
- * Closes the black hole: when the browser has no Web Crypto, OpenFort's
2780
- * `configure` would resolve to no address and the failure would be reported
2781
- * as `unknown`. Detect it before any network or wallet work. Tag it
2782
- * `no-secure-context` on a PROVIDER_ERROR scoped to `configure`. Add a
2783
- * DiagnosticPort breadcrumb. The assembled Core SDK boundary reports it.
2784
- */
2785
- function failNoSecureContext() {
2786
- diagnostic?.trace("openfort.configure", {
2787
- ok: false,
2788
- failure_mode: "no-secure-context"
2789
- });
2790
- throw failOpenfort("configure", "no-secure-context", /* @__PURE__ */ new Error("Web Crypto unavailable: browser is not a secure context"));
2791
- }
2792
- function betterAuthSessionUrl() {
2793
- return `${authBaseUrl}/get-session`;
2794
- }
2795
- function encryptionSessionUrl() {
2796
- return `${authBaseUrl}/encryption-session`;
2797
- }
2798
- async function fetchBetterAuthAccessToken() {
2799
- try {
2800
- const response = await fetch(betterAuthSessionUrl(), { credentials: "include" });
2801
- if (!response.ok) {
2802
- diagnostic?.trace("openfort.token", {
2803
- ok: false,
2804
- tokenPresent: false,
2805
- httpStatus: response.status,
2806
- failure_mode: "unknown"
2807
- });
2808
- return null;
2809
- }
2810
- const token = (await response.json()).session?.token?.trim();
2811
- if (token === void 0 || token.length === 0) {
2812
- diagnostic?.trace("openfort.token", {
2813
- ok: false,
2814
- tokenPresent: false,
2815
- failure_mode: "unknown"
2816
- });
2817
- return null;
2818
- }
2819
- diagnostic?.trace("openfort.token", {
2820
- ok: true,
2821
- tokenPresent: true
2822
- });
2823
- return token;
2824
- } catch (cause) {
2825
- diagnostic?.trace("openfort.token", {
2826
- ok: false,
2827
- tokenPresent: false,
2828
- failure_mode: "unknown"
2829
- });
2830
- throw openfortProviderError("token", cause);
2831
- }
2832
- }
2833
- const openfort = new Openfort({
2834
- baseConfiguration: { publishableKey: bootstrap.openfortPublishableKey },
2835
- shieldConfiguration: { shieldPublishableKey: bootstrap.shieldPublishableKey },
2836
- thirdPartyAuth: {
2837
- provider: ThirdPartyOAuthProvider.BETTER_AUTH,
2838
- getAccessToken: fetchBetterAuthAccessToken
2839
- }
2840
- });
2841
- async function configureEmbeddedWallet(encryptionSession) {
2842
- await openfort.embeddedWallet.configure({
2843
- accountType: AccountTypeEnum.EOA,
2844
- chainType: ChainTypeEnum.EVM,
2845
- recoveryParams: {
2846
- recoveryMethod: RecoveryMethod.AUTOMATIC,
2847
- encryptionSession
2848
- }
2849
- });
2850
- }
2851
- /**
2852
- * ONE heal cycle on the stale-user rejection (#1435). Clear the scoped
2853
- * storage that pins the dead `userId`, run the existing configure path — it
2854
- * re-runs third-party auth against the live Better Auth session now that no
2855
- * `userId` is cached — and retry the read once. Exactly one cycle: a second
2856
- * rejection is terminal and carries `failure_mode: "stale-openfort-cache"`.
2857
- * The caller stays in `recovering` throughout; only the outcome moves it.
2858
- */
2859
- async function healStaleOpenfortCache(encryptionSession) {
2860
- try {
2861
- clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
2862
- diagnostic?.trace("openfort.storageCleared", { beforeConfigure: false });
2863
- await configureEmbeddedWallet(encryptionSession);
2864
- await openfort.embeddedWallet.get();
2865
- } catch (cause) {
2866
- diagnostic?.trace("openfort.staleCacheHeal", {
2867
- ok: false,
2868
- failure_mode: "stale-openfort-cache"
2869
- });
2870
- throw failOpenfort("get", "stale-openfort-cache", cause);
2871
- }
2872
- diagnostic?.trace("openfort.staleCacheHeal", { ok: true });
2873
- }
2874
- let walletReadyPromise = null;
2875
- function startWalletReady() {
2876
- return (async () => {
2877
- if (isInsecureBrowserContext()) failNoSecureContext();
2878
- await openfort.waitForInitialization();
2879
- const accessToken = await fetchBetterAuthAccessToken();
2880
- if (accessToken === null) throw openfortProviderError("token", /* @__PURE__ */ new Error("Better Auth access token unavailable for Openfort"));
2881
- let encryptionResponse;
2882
- try {
2883
- encryptionResponse = await fetch(encryptionSessionUrl(), {
2884
- method: "POST",
2885
- credentials: "include",
2886
- headers: {
2887
- Authorization: `Bearer ${accessToken}`,
2888
- "Content-Type": "application/json"
2889
- },
2890
- body: JSON.stringify({})
2891
- });
2892
- } catch (cause) {
2893
- diagnostic?.trace("openfort.encryptionSession", {
2894
- ok: false,
2895
- failure_mode: "unknown"
2896
- });
2897
- throw openfortProviderError("encryptionSession", cause);
2898
- }
2899
- if (!encryptionResponse.ok) {
2900
- diagnostic?.trace("openfort.encryptionSession", {
2901
- ok: false,
2902
- httpStatus: encryptionResponse.status,
2903
- failure_mode: "unknown"
2904
- });
2905
- throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error(`Openfort encryption session failed (${encryptionResponse.status})`), { failure_mode: "unknown" });
2906
- }
2907
- let encryptionBody;
2908
- try {
2909
- encryptionBody = await encryptionResponse.json();
2910
- if (typeof encryptionBody.sessionId !== "string" || encryptionBody.sessionId.length === 0) throw new Error("Openfort encryption session response missing sessionId");
2911
- } catch (cause) {
2912
- diagnostic?.trace("openfort.encryptionSession", {
2913
- ok: false,
2914
- httpStatus: encryptionResponse.status,
2915
- failure_mode: "unknown"
2916
- });
2917
- throw openfortProviderError("encryptionSession", cause);
2918
- }
2919
- diagnostic?.trace("openfort.encryptionSession", {
2920
- ok: true,
2921
- httpStatus: encryptionResponse.status
2922
- });
2923
- let embeddedState;
2924
- try {
2925
- embeddedState = await openfort.embeddedWallet.getEmbeddedState();
2926
- diagnostic?.trace("openfort.embeddedState", { state: embeddedState });
2927
- } catch (cause) {
2928
- diagnostic?.trace("openfort.embeddedState", {
2929
- ok: false,
2930
- failure_mode: "unknown"
2931
- });
2932
- throw openfortProviderError("embeddedState", cause);
2933
- }
2934
- if (embeddedState !== EmbeddedState.READY) {
2935
- clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
2936
- diagnostic?.trace("openfort.storageCleared", { beforeConfigure: true });
2937
- try {
2938
- await configureEmbeddedWallet(encryptionBody.sessionId);
2939
- diagnostic?.trace("openfort.configure", { ok: true });
2940
- } catch (cause) {
2941
- const error = openfortProviderError("configure", cause);
2942
- diagnostic?.trace("openfort.configure", {
2943
- ok: false,
2944
- failure_mode: openfortFailureMode(error)
2945
- });
2946
- throw error;
2947
- }
2948
- }
2949
- try {
2950
- await openfort.embeddedWallet.get();
2951
- diagnostic?.trace("openfort.get", { ok: true });
2952
- } catch (cause) {
2953
- if (!isStaleUserSignal(cause)) {
2954
- diagnostic?.trace("openfort.get", {
2955
- ok: false,
2956
- failure_mode: "unknown"
2957
- });
2958
- throw openfortProviderError("get", cause);
2959
- }
2960
- diagnostic?.trace("openfort.get", {
2961
- ok: false,
2962
- failure_mode: "stale-openfort-cache"
2963
- });
2964
- await healStaleOpenfortCache(encryptionBody.sessionId);
2965
- }
2966
- })();
2967
- }
2968
- async function ensureOpenfortWalletReady() {
2969
- let joined = walletReadyPromise;
2970
- if (joined === null) {
2971
- joined = startWalletReady();
2972
- walletReadyPromise = joined;
2973
- setStatus("recovering");
2974
- }
2975
- try {
2976
- await joined;
2977
- if (walletReadyPromise === joined) setStatus("ready");
2978
- } catch (cause) {
2979
- if (walletReadyPromise === joined) {
2980
- walletReadyPromise = null;
2981
- setStatus("unavailable");
2982
- }
2983
- throw cause;
2984
- }
2985
- }
2986
- const signer = openfortEmbeddedSignerFromWallet({
2987
- embeddedWallet: openfort.embeddedWallet,
2988
- ensureWalletReady: ensureOpenfortWalletReady
2989
- });
2990
- const resetReadiness = () => {
2991
- walletReadyPromise = null;
2992
- clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
2993
- signer.resetAddressCache();
2994
- setStatus("unknown");
2995
- };
2996
- const signUserOpHash = async (hash) => {
2997
- try {
2998
- return await signer.signUserOpHash(hash);
2999
- } catch (cause) {
3000
- const providerErrorCode = openfortSignerNotReadyCode(cause);
3001
- if (providerErrorCode === void 0) throw cause;
3002
- diagnostic?.trace("openfort.signerRecovery", {
3003
- outcome: "started",
3004
- attempt: 1,
3005
- failure_mode: "signer-not-ready"
3006
- });
3007
- resetReadiness();
3008
- try {
3009
- await ensureOpenfortWalletReady();
3010
- const signature = await signer.signUserOpHash(hash);
3011
- diagnostic?.trace("openfort.signerRecovery", {
3012
- outcome: "succeeded",
3013
- attempt: 1,
3014
- failure_mode: "signer-not-ready"
3015
- });
3016
- return signature;
3017
- } catch (retryCause) {
3018
- const retryFailure = signerFailure("openfort-embedded", "signUserOpHash", retryCause);
3019
- if (retryFailure.code === "SIGNER_REJECTED") throw retryFailure;
3020
- if (isSignerNotReadySignal(retryCause)) {
3021
- walletReadyPromise = null;
3022
- signer.resetAddressCache();
3023
- setStatus("unavailable");
3024
- }
3025
- diagnostic?.trace("openfort.signerRecovery", {
3026
- outcome: "failed",
3027
- attempt: 1,
3028
- failure_mode: isSignerNotReadySignal(retryCause) ? "signer-not-ready" : "unknown"
3029
- });
3030
- throw Errors.providerError("openfort", "signerRecovery", retryCause, {
3031
- failure_mode: "signer-not-ready",
3032
- details: {
3033
- provider_error_source: "openfort",
3034
- provider_error_code: providerErrorCode
3035
- }
3036
- });
3037
- }
3038
- }
3039
- };
3040
- return {
3041
- ...signer,
3042
- signUserOpHash,
3043
- statusStore,
3044
- ensureWalletReady: ensureOpenfortWalletReady,
3045
- getAddress: async () => {
3046
- try {
3047
- const address = await signer.getAddress();
3048
- diagnostic?.trace("openfort.address", { ok: true });
3049
- return address;
3050
- } catch (cause) {
3051
- diagnostic?.trace("openfort.address", {
3052
- ok: false,
3053
- failure_mode: "unknown"
3054
- });
3055
- throw openfortProviderError("getAddress", cause);
3056
- }
3057
- },
3058
- resetSession: resetReadiness
3059
- };
3060
- }
3061
- function normalizeBetterAuthBaseUrl(raw) {
3062
- const trimmed = raw.replace(/\/$/, "");
3063
- return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
3064
- }
3065
- //#endregion
3066
- //#region src/telemetry/from-posthog.ts
3067
- /**
3068
- * Drop props that must never cross to a host-owned external sink. Today that is
3069
- * the `$exception` `details` blob: `captureException` serializes
3070
- * `CapxulError.details` (e.g. `{ asset, available, required }`, `{ name }`,
3071
- * `{ accountId }` — errors.ts) into it, and the shared redactor has no
3072
- * `$exception` rule, so it is stripped here at the boundary (infra#1037). The
3073
- * safe fields (error code, operation, failure_mode, the fixed leak-safe message,
3074
- * stack frames) are preserved.
3075
- */
3076
- function stripHostUnsafeProps(props) {
3077
- if (props === void 0) return void 0;
3078
- const { details: _details, ...safe } = props;
3079
- return safe;
3080
- }
3081
- /**
3082
- * The subset of a posthog-js client the seam calls. `identify` / `group` /
3083
- * `reset` are optional — a host that only wants event capture can omit them.
3084
- */
3085
- /**
3086
- * Adapt the host's already-initialized posthog-like client into a
3087
- * `TelemetryPort` for the `telemetry` prop / input. This port SUPPLANTS the
3088
- * SDK's no-op default telemetry sink (`production.ts` binds it via
3089
- * `Layer.succeed`, not `compose` — there is no client-side success relay to
3090
- * compose with); it is additive to Capxul's backend first-party record and
3091
- * never owns the client.
3092
- */
3093
- function postHogProductTelemetry(policy, fixedSnapshot) {
3094
- const telemetry = new PostHogTelemetryAdapter({
3095
- capture: (name, props, source) => {
3096
- const snapshot = readInvocationObservation(source) ?? fixedSnapshot ?? policy.snapshot();
3097
- if (!snapshot.active || policy.client === null || policy.client === void 0) return;
3098
- const event = stampTelemetryEnvelope({
3099
- name,
3100
- props
3101
- }, {
3102
- capxul_env: policy.capxulEnv,
3103
- producer: "sdk"
3104
- });
3105
- policy.deliver(() => policy.client.capture(name, {
3106
- ...stripHostUnsafeProps(event.props),
3107
- ...observationContextProps(snapshot.context)
3108
- }));
3109
- },
3110
- identify: (input) => {
3111
- if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.identify === void 0) return;
3112
- policy.deliver(() => policy.client.identify(input.distinctId, {
3113
- ...input.traits,
3114
- ...input.properties
3115
- }));
3116
- },
3117
- group: (input) => {
3118
- if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.group === void 0) return;
3119
- policy.deliver(() => policy.client.group(input.groupType, input.groupKey, input.properties === void 0 ? void 0 : { ...input.properties }));
3120
- },
3121
- reset: () => {
3122
- if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.reset === void 0) return;
3123
- policy.deliver(() => policy.client.reset());
3124
- }
3125
- });
3126
- Object.defineProperty(telemetry, PRODUCT_INVOCATION, {
3127
- enumerable: false,
3128
- value: (source) => postHogProductTelemetry(policy, readInvocationObservation(source) ?? fixedSnapshot ?? policy.snapshot())
3129
- });
3130
- return telemetry;
3131
- }
3132
- //#endregion
3133
- //#region src/host-observability.ts
3134
- const HOST_INVOCATION = Symbol("capxul.host-observability-invocation");
3135
- /** Build the SDK's one host module around an already-initialized PostHog client. */
3136
- function postHogObservability(client, options = {}) {
3137
- const inactive = Object.freeze({
3138
- active: false,
3139
- contextProps: Object.freeze({})
3140
- });
3141
- const snapshot = () => {
3142
- if (client === null || client === void 0) return inactive;
3143
- try {
3144
- if (!(typeof options.enabled === "function" ? options.enabled() : options.enabled ?? true)) return inactive;
3145
- const rawContext = typeof options.context === "function" ? options.context() : options.context;
3146
- const sanitized = sanitizeObservationContext(rawContext);
3147
- const context = sanitized === void 0 ? void 0 : Object.freeze({ ...sanitized });
3148
- return Object.freeze({
3149
- active: true,
3150
- ...context === void 0 ? {} : { context },
3151
- contextProps: Object.freeze(observationContextProps(context))
3152
- });
3153
- } catch {
3154
- return inactive;
3155
- }
3156
- };
3157
- const policy = {
3158
- client,
3159
- capxulEnv: options.capxulEnv ?? "unknown",
3160
- snapshot,
3161
- deliver: (capture) => {
3162
- try {
3163
- const delivery = capture();
3164
- if (isPromiseLike(delivery)) Promise.resolve(delivery).catch(() => void 0);
3165
- } catch {}
3166
- }
3167
- };
3168
- const module = {
3169
- failures: postHogFailureObservation(policy),
3170
- product: postHogProductTelemetry(policy)
3171
- };
3172
- Object.defineProperty(module, HOST_INVOCATION, {
3173
- enumerable: false,
3174
- value: {
3175
- bind: () => {
3176
- const invocation = snapshot();
3177
- return {
3178
- failures: postHogFailureObservation(policy, invocation),
3179
- product: postHogProductTelemetry(policy, invocation)
3180
- };
3181
- },
3182
- snapshot
3183
- }
3184
- });
3185
- return module;
3186
- }
3187
- /** @internal Bind both projections to one call-start decision/context snapshot. */
3188
- function bindHostObservabilityInvocation(observability) {
3189
- return observability?.[HOST_INVOCATION]?.bind() ?? observability;
3190
- }
3191
- /** @internal Read one call-start snapshot for actor/transition carriage. */
3192
- function snapshotHostObservability(observability) {
3193
- const snapshot = observability?.[HOST_INVOCATION]?.snapshot();
3194
- return snapshot === void 0 ? void 0 : {
3195
- active: snapshot.active,
3196
- ...snapshot.context === void 0 ? {} : { context: snapshot.context }
3197
- };
3198
- }
3199
- function isPromiseLike(value) {
3200
- return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value;
3201
- }
3202
- const SDK_VERSION = version;
3203
- /**
3204
- * Project the built graph into the flat `FlowPorts` record the method bundles
3205
- * still take. It is a VIEW of `ProductionAdapters.context`, taken from the
3206
- * graph and never instead of it — the graph stays alive in the scope and is
3207
- * handed to every caller.
3208
- */
3209
- const collectProductionFlowPorts = Effect.gen(function* () {
3210
- const authClient = yield* AuthClientPortTag;
3211
- const authCache = yield* AuthCachePortTag;
3212
- const bootstrap = yield* BootstrapPortTag;
3213
- const clock = yield* ClockPortTag;
3214
- const convexCall = yield* ConvexCallPortTag;
3215
- return {
3216
- authClient,
3217
- authCache,
3218
- identity: yield* IdentityPortTag,
3219
- smartAccount: yield* SmartAccountPortTag,
3220
- accountRead: yield* AccountReadPortTag,
3221
- bootstrap,
3222
- clock,
3223
- telemetry: yield* TelemetryPortTag,
3224
- convexCall
3225
- };
3226
- });
3227
- function makeProductionAdapterLayerEntries(input) {
3228
- const refreshConvexAuthRef = {
3229
- current: null,
3230
- pending: false
3231
- };
3232
- return [
3233
- {
3234
- name: "bootstrap",
3235
- layer: productionBootstrapPortLayer(input.bootstrap)
3236
- },
3237
- {
3238
- name: "authClient",
3239
- layer: productionAuthClientLayer(input, refreshConvexAuthRef, input.resetSignerSession)
3240
- },
3241
- {
3242
- name: "authCache",
3243
- layer: productionAuthCacheLayer(input)
3244
- },
3245
- {
3246
- name: "identity",
3247
- layer: ConvexIdentityLayer()
3248
- },
3249
- {
3250
- name: "smartAccount",
3251
- layer: ConvexSmartAccountLayer()
3252
- },
3253
- {
3254
- name: "accountRead",
3255
- layer: ConvexAccountLayer()
3256
- },
3257
- {
3258
- name: "clock",
3259
- layer: SystemClockLayer()
3260
- },
3261
- {
3262
- name: "telemetry",
3263
- layer: input.telemetry === void 0 ? PostHogTelemetryLayer({ capture: () => void 0 }) : Layer.succeed(TelemetryPortTag, input.telemetry)
3264
- },
3265
- {
3266
- name: "convexCall",
3267
- layer: productionConvexCallLayer(input, refreshConvexAuthRef)
3268
- },
3269
- {
3270
- name: "org",
3271
- layer: Layer.effect(OrgPortTag, Effect.all([ConvexCallPortTag, TelemetryPortTag]).pipe(Effect.map(([convex, telemetry]) => new ConvexOrganizationAdapter({
3272
- convex,
3273
- telemetry
3274
- }))))
3275
- }
3276
- ];
3277
- }
3278
- function mergeProductionAdapterLayers(entries) {
3279
- const byName = /* @__PURE__ */ new Map();
3280
- for (const entry of entries) byName.set(entry.name, entry.layer);
3281
- const authClientLayer = byName.get("authClient");
3282
- const convexCallLayer = byName.get("convexCall");
3283
- const telemetryLayer = byName.get("telemetry");
3284
- const convexCallReadyLayer = convexCallLayer === void 0 || authClientLayer === void 0 ? convexCallLayer : convexCallLayer.pipe(Layer.provide(authClientLayer));
3285
- const observedConvexCallLayer = convexCallReadyLayer === void 0 ? telemetryLayer : telemetryLayer === void 0 ? convexCallReadyLayer : Layer.merge(convexCallReadyLayer, telemetryLayer);
3286
- return entries.map((entry) => {
3287
- const layer = byName.get(entry.name) ?? entry.layer;
3288
- if (entry.name === "convexCall") return convexCallReadyLayer ?? layer;
3289
- if (entry.name === "accountRead" || entry.name === "org") return observedConvexCallLayer === void 0 ? layer : layer.pipe(Layer.provide(observedConvexCallLayer));
3290
- if (isConvexDependentLayer(entry.name)) return convexCallReadyLayer === void 0 ? layer : layer.pipe(Layer.provide(convexCallReadyLayer));
3291
- return layer;
3292
- }).reduce((current, layer) => Layer.merge(current, layer), Layer.empty);
3293
- }
3294
- function isConvexDependentLayer(name) {
3295
- return name === "identity" || name === "smartAccount" || name === "accountRead" || name === "org";
3296
- }
3297
- function productionBootstrapPortLayer(bootstrap) {
3298
- return Layer.succeed(BootstrapPortTag, bootstrap);
3299
- }
3300
- function productionAuthClientLayer(input, refreshConvexAuthRef, resetSignerSessionRef) {
3301
- return (input.authClient !== void 0 ? Layer.succeed(AuthClientPortTag, input.authClient) : input.runtime === "browser" ? BetterAuthBrowserLayer({
3302
- authBaseUrl: input.runtimeUrls.authBaseUrl,
3303
- ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3304
- ...input.observation === void 0 ? {} : { observation: input.observation }
3305
- }) : BetterAuthNodeLayer({
3306
- authBaseUrl: input.runtimeUrls.authBaseUrl,
3307
- ...input.origin === void 0 ? {} : { origin: input.origin },
3308
- ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3309
- ...input.observation === void 0 ? {} : { observation: input.observation }
3310
- })).pipe(Layer.flatMap((context) => {
3311
- const refreshed = refreshConvexAuthOnSession(Context.get(context, AuthClientPortTag), () => {
3312
- const refresh = refreshConvexAuthRef.current;
3313
- if (refresh === null) {
3314
- refreshConvexAuthRef.pending = true;
3315
- return;
3316
- }
3317
- refresh();
3318
- });
3319
- return Layer.succeedContext(Context.make(AuthClientPortTag, resetSignerSessionRef === void 0 ? refreshed : resetSignerSessionOnSignOut(refreshed, resetSignerSessionRef)));
3320
- }));
3321
- }
3322
- function productionAuthCacheLayer(input) {
3323
- return Layer.succeed(AuthCachePortTag, input.authCache ?? detectAuthCacheAdapter());
3324
- }
3325
- function productionConvexCallLayer(input, refreshConvexAuthRef) {
3326
- return Layer.unwrap(Effect.map(AuthClientPortTag, (authClient) => ConvexCallLayer({
3327
- convexUrl: input.runtimeUrls.convexUrl,
3328
- ...input.applicationId === void 0 ? {} : { applicationId: input.applicationId },
3329
- ...input.observation === void 0 ? {} : { observation: input.observation },
3330
- ...input.convexClient === void 0 ? {} : { client: input.convexClient },
3331
- tokenProvider: async ({ forceRefreshToken }) => {
3332
- const tokenResult = await Effect.runPromise(Effect.result(authClient.getConvexJwt({
3333
- forceRefresh: forceRefreshToken,
3334
- ...input.signal === void 0 ? {} : { signal: input.signal }
3335
- })));
3336
- if (Result.isFailure(tokenResult)) return null;
3337
- return String(tokenResult.success.token);
3338
- }
3339
- }).pipe(Layer.flatMap((context) => {
3340
- const convexCall = Context.get(context, ConvexCallPortTag);
3341
- if (isRefreshableConvexCallPort(convexCall)) {
3342
- refreshConvexAuthRef.current = () => convexCall.refreshAuth();
3343
- if (refreshConvexAuthRef.pending) {
3344
- refreshConvexAuthRef.pending = false;
3345
- refreshConvexAuthRef.current();
3346
- }
3347
- }
3348
- return Layer.succeedContext(context);
3349
- }))));
3350
- }
3351
- function isRefreshableConvexCallPort(convexCall) {
3352
- return "refreshAuth" in convexCall && typeof convexCall.refreshAuth === "function";
3353
- }
3354
- function resolveProductionObservability(input) {
3355
- const observation = input.observability?.failures ?? input.observation;
3356
- const telemetry = input.observability?.product ?? input.telemetry;
3357
- const invocationObservability = input.invocationObservability;
3358
- return {
3359
- observation,
3360
- telemetry,
3361
- invocationObservation: invocationObservability?.failures ?? observation,
3362
- invocationTelemetry: invocationObservability?.product ?? telemetry
3363
- };
3364
- }
3365
- async function createProductionAdapters(input) {
3366
- const { observation, telemetry, invocationObservation, invocationTelemetry } = resolveProductionObservability(input);
3367
- const resolvedInput = resolveInput(input);
3368
- if (!resolvedInput.ok) return resolvedInput;
3369
- const scope = await Effect.runPromise(Scope.make());
3370
- const closeScope = idempotentClose(() => Effect.runPromise(Scope.close(scope, Exit.void)));
3371
- const bootstrapLayer = HttpBootstrapLayer({
3372
- bootstrapBaseUrl: resolvedInput.value.bootstrapBaseUrl,
3373
- ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3374
- ...invocationObservation === void 0 ? {} : { observation: invocationObservation }
3375
- });
3376
- try {
3377
- const bootstrapContext = await Effect.runPromise(Layer.buildWithScope(bootstrapLayer, scope));
3378
- const bootstrap = Context.get(bootstrapContext, BootstrapPortTag);
3379
- const bootstrapResult = await runBootstrap(bootstrap.resolve({
3380
- publishableKey: resolvedInput.value.publishableKey,
3381
- ...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin }
3382
- }));
3383
- if (!bootstrapResult.ok) {
3384
- await emitBootstrapTelemetry(invocationTelemetry, {
3385
- name: "bootstrap_failed",
3386
- props: {
3387
- ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
3388
- reason: bootstrapResult.error.code,
3389
- ...failureDetail(bootstrapResult.error),
3390
- ...failureEvidenceProps(bootstrapResult.error)
3391
- }
3392
- });
3393
- await closeScope().catch(() => void 0);
3394
- return bootstrapResult;
3395
- }
3396
- const chainFence = assertDevKeySignerIsTestnetOnly(input.signer, bootstrapResult.value.chainId);
3397
- if (!chainFence.ok) {
3398
- await closeScope().catch(() => void 0);
3399
- return chainFence;
3400
- }
3401
- const runtimeUrlsResult = resolveRuntimeUrls(bootstrapResult.value, resolvedInput.value.runtime, input.authBaseUrl);
3402
- if (!runtimeUrlsResult.ok) {
3403
- await emitBootstrapTelemetry(invocationTelemetry, {
3404
- name: "bootstrap_failed",
3405
- props: {
3406
- ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
3407
- applicationId: bootstrapResult.value.applicationId,
3408
- reason: runtimeUrlsResult.error.code
3409
- }
3410
- });
3411
- await closeScope().catch(() => void 0);
3412
- return runtimeUrlsResult;
3413
- }
3414
- const runtimeUrls = runtimeUrlsResult;
3415
- await emitBootstrapTelemetry(invocationTelemetry, {
3416
- name: "bootstrap_resolved",
3417
- props: {
3418
- ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
3419
- applicationId: bootstrapResult.value.applicationId
3420
- }
3421
- });
3422
- let injectedClient;
3423
- const convexClientFactory = input.convexClientFactory;
3424
- if (convexClientFactory !== void 0) try {
3425
- injectedClient = convexClientFactory(runtimeUrls.value.convexUrl);
3426
- } catch (cause) {
3427
- await closeScope().catch(() => void 0);
3428
- return {
3429
- ok: false,
3430
- error: toPublicError(cause, "createProductionAdapters")
3431
- };
3432
- }
3433
- const portsLayer = mergeProductionAdapterLayers(makeProductionAdapterLayerEntries({
3434
- bootstrap,
3435
- runtime: resolvedInput.value.runtime,
3436
- ...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin },
3437
- runtimeUrls: runtimeUrls.value,
3438
- chainId: bootstrapResult.value.chainId,
3439
- applicationId: bootstrapResult.value.applicationId,
3440
- ...observation === void 0 ? {} : { observation },
3441
- ...input.authCache === void 0 ? {} : { authCache: input.authCache },
3442
- ...input.authClient === void 0 ? {} : { authClient: input.authClient },
3443
- ...telemetry === void 0 ? {} : { telemetry },
3444
- ...injectedClient === void 0 ? {} : { convexClient: injectedClient },
3445
- ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3446
- ...input.signal === void 0 ? {} : { signal: input.signal },
3447
- ...input.resetSignerSession === void 0 ? {} : { resetSignerSession: input.resetSignerSession }
3448
- }));
3449
- const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime));
3450
- const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
3451
- const ports = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
3452
- const stopConnectionObservation = observeProductionConvexConnection(ports.convexCall, context);
3453
- const close = idempotentClose(async () => {
3454
- try {
3455
- stopConnectionObservation();
3456
- } finally {
3457
- await closeScope();
3458
- }
3459
- });
3460
- return {
3461
- ok: true,
3462
- value: {
3463
- context,
3464
- ports,
3465
- bootstrap: bootstrapResult.value,
3466
- close
3467
- }
3468
- };
3469
- } catch (cause) {
3470
- await closeScope().catch(() => void 0);
3471
- return {
3472
- ok: false,
3473
- error: toPublicError(cause, "createProductionAdapters")
3474
- };
3475
- }
3476
- }
3477
- function observeProductionConvexConnection(convexCall, context) {
3478
- if (!("observeConnection" in convexCall) || typeof convexCall.observeConnection !== "function") return () => {};
3479
- const runPromise = Effect.runPromiseWith(context);
3480
- try {
3481
- return convexCall.observeConnection((diagnostic) => {
3482
- const message = diagnostic.transition === "connected" ? Effect.logInfo("convex.connection.changed") : Effect.logWarning("convex.connection.changed");
3483
- runPromise(message.pipe(Effect.annotateLogs(diagnostic), Effect.catchCause(() => Effect.void))).catch(() => void 0);
3484
- });
3485
- } catch {
3486
- return () => {};
3487
- }
3488
- }
3489
- function optionalEngineeringTelemetryLayer(policy, runtime) {
3490
- if (policy === void 0) return Layer.empty;
3491
- try {
3492
- return makeEngineeringTelemetryLayer({
3493
- host: policy.host,
3494
- headers: { authorization: `Bearer ${policy.projectToken}` },
3495
- capxulEnv: policy.capxulEnv,
3496
- producer: runtime === "browser" ? "browser" : "server",
3497
- sdkVersion: SDK_VERSION
3498
- });
3499
- } catch {
3500
- return Layer.empty;
3501
- }
3502
- }
3503
- function refreshConvexAuthOnSession(authClient, refresh) {
3504
- return {
3505
- ...authClient,
3506
- verifyOtp: (input, options) => authClient.verifyOtp(input, options).pipe(Effect.tap(() => Effect.sync(refresh))),
3507
- signOut: (options) => authClient.signOut(options).pipe(Effect.tap(() => Effect.sync(refresh)))
3508
- };
3509
- }
3510
- /**
3511
- * Signer-session reset, as a port wrapper INSIDE the Layer graph (blueprint §2:
3512
- * "wrappers become Layers, not post-hoc spreads/Proxies").
3513
- *
3514
- * This was a spread over the assembled client — `{...client, auth: {...,
3515
- * signOut}}` — applied after composition finished, so the reset lived on one
3516
- * particular object rather than on the auth seam itself. Here it wraps
3517
- * `AuthClientPort.signOut`, so it holds for every route to a sign-out.
3518
- *
3519
- * Capture the sign-out exit before reset. This preserves the old `try/finally`
3520
- * order and keeps a reset failure in the typed error channel.
3521
- */
3522
- function resetSignerSessionOnSignOut(authClient, resetRef) {
3523
- return {
3524
- ...authClient,
3525
- signOut: (options) => Effect.gen(function* () {
3526
- const exit = yield* Effect.exit(authClient.signOut(options));
3527
- yield* Effect.try({
3528
- try: () => resetRef.current?.(),
3529
- catch: (cause) => new AuthClientError({
3530
- operation: "signOut",
3531
- kind: "signer",
3532
- cause
3533
- })
3534
- });
3535
- return yield* exit;
3536
- })
3537
- };
3538
- }
3539
- async function createCapxulClient$1(input) {
3540
- const invocationObservability = bindHostObservabilityInvocation(input.observability);
3541
- const observation = invocationObservability?.failures ?? input.observation;
3542
- const validation = validateCreateCapxulClientInput(input);
3543
- if (!validation.ok) return observeFailedResult(validation, observation, "createCapxulClient");
3544
- const resetSignerSession = { current: null };
3545
- const adapters = await createProductionAdapters({
3546
- ...input,
3547
- resetSignerSession,
3548
- ...invocationObservability === void 0 ? {} : { invocationObservability }
3549
- });
3550
- if (!adapters.ok) return observeFailedResult(adapters, observation, "createCapxulClient");
3551
- try {
3552
- const runtime = input.runtime ?? detectRuntime();
3553
- const resolvedAuthBaseUrl = resolveBrowserAuthBaseUrl({
3554
- bootstrapAuthBaseUrl: adapters.value.bootstrap.authBaseUrl,
3555
- runtime,
3556
- ...input.authBaseUrl === void 0 ? {} : { override: input.authBaseUrl }
3557
- });
3558
- let signer = input.signer;
3559
- if (signer === void 0 && runtime === "browser") signer = createOpenfortBrowserSignerFromBootstrap({
3560
- ...adapters.value.bootstrap,
3561
- authBaseUrl: resolvedAuthBaseUrl
3562
- }, { diagnostic: new ConsoleDiagnosticAdapter() });
3563
- if (signer !== void 0 && "resetSession" in signer) {
3564
- const { resetSession, source } = signer;
3565
- resetSignerSession.current = () => {
3566
- try {
3567
- resetSession();
3568
- } catch (cause) {
3569
- throw signerFailure(source, "resetSession", cause);
3570
- }
3571
- };
3572
- }
3573
- const client = assembleCapxulClient({
3574
- ports: adapters.value.ports,
3575
- bootstrap: adapters.value.bootstrap,
3576
- authCache: adapters.value.ports.authCache,
3577
- requirement: input.requirement ?? "none",
3578
- ...signer === void 0 ? {} : { signer },
3579
- orgPort: Context.get(adapters.value.context, OrgPortTag),
3580
- ...signer === void 0 ? {} : { organizationSetup: new ConvexOrganizationSetupAdapter({
3581
- convex: adapters.value.ports.convexCall,
3582
- signer,
3583
- chainId: adapters.value.bootstrap.chainId
3584
- }) },
3585
- ...input.signal === void 0 ? {} : { signal: input.signal },
3586
- ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
3587
- invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4,
3588
- ...input.observability?.failures === void 0 && input.observation === void 0 ? {} : { failureObservation: input.observability?.failures ?? input.observation },
3589
- ...input.observability === void 0 ? {} : { hostObservationSnapshot: () => snapshotHostObservability(input.observability) },
3590
- effectRunner: {
3591
- runSync: Effect.runSyncWith(adapters.value.context),
3592
- runPromise: Effect.runPromiseWith(adapters.value.context)
3593
- }
3594
- });
3595
- const upstreamClose = adapters.value.close;
3596
- const close = idempotentClose(async () => {
3597
- try {
3598
- if (signer !== void 0 && "resetSession" in signer) try {
3599
- signer.resetSession();
3600
- } catch (cause) {
3601
- throw signerFailure(signer.source, "resetSession", cause);
3602
- }
3603
- } finally {
3604
- await Promise.all([client._internal.close?.(), upstreamClose()]);
3605
- }
3606
- });
3607
- return {
3608
- ok: true,
3609
- value: {
3610
- ...client,
3611
- _internal: {
3612
- ...client._internal,
3613
- close
3614
- }
3615
- }
3616
- };
3617
- } catch (cause) {
3618
- await adapters.value.close().catch(() => void 0);
3619
- return observeFailedResult({
3620
- ok: false,
3621
- error: toPublicError(cause, "createCapxulClient")
3622
- }, observation, "createCapxulClient");
3623
- }
3624
- }
3625
- function validateCreateCapxulClientInput(input) {
3626
- const runtime = input.runtime ?? detectRuntime();
3627
- if ((input.requirement ?? "none") === "deployed" && input.signer === void 0 && runtime !== "browser") return {
3628
- ok: false,
3629
- error: Errors.invalidInput("signer", "required when requirement is \"deployed\"")
3630
- };
3631
- return {
3632
- ok: true,
3633
- value: void 0
3634
- };
3635
- }
3636
- function resolveInput(input) {
3637
- try {
3638
- const runtime = input.runtime ?? detectRuntime();
3639
- const publishableKey = toPublishableKey(input.publishableKey);
3640
- const origin = input.origin === void 0 ? runtime === "browser" ? toAllowedOrigin(derivedBrowserOrigin(runtime)) : void 0 : toAllowedOrigin(input.origin);
3641
- return {
3642
- ok: true,
3643
- value: {
3644
- publishableKey,
3645
- ...origin === void 0 ? {} : { origin },
3646
- bootstrapBaseUrl: normalizeHttpUrl("bootstrapBaseUrl", input.bootstrapBaseUrl ?? (runtime === "browser" ? derivedBrowserOrigin(runtime) : "https://api.capxul.com")),
3647
- runtime
3648
- }
3649
- };
3650
- } catch (cause) {
3651
- return {
3652
- ok: false,
3653
- error: toPublicError(cause, "createProductionAdapters")
3654
- };
3655
- }
3656
- }
3657
- function detectRuntime() {
3658
- const globalAny = globalThis;
3659
- return globalAny.window !== void 0 || globalAny.document !== void 0 ? "browser" : "node";
3660
- }
3661
- function derivedBrowserOrigin(runtime) {
3662
- if (runtime !== "browser") throw Errors.invalidInput("origin", "required outside browser runtime");
3663
- const globalAny = globalThis;
3664
- if (typeof globalAny.location?.origin === "string" && globalAny.location.origin.length > 0) return globalAny.location.origin;
3665
- throw Errors.invalidInput("origin", "required when browser location is unavailable");
3666
- }
3667
- /**
3668
- * Browser local dev serves `/api/auth` via the Vite proxy on `window.location.origin`
3669
- * while bootstrap returns the remote Convex site host. Openfort wallet setup reads
3670
- * Better Auth cookies from `get-session` — those only attach on the same origin the
3671
- * OTP flow used, so rewrite when hosts differ.
3672
- */
3673
- function resolveBrowserAuthBaseUrl(input) {
3674
- if (input.override !== void 0) return normalizeHttpUrl("authBaseUrl", input.override);
3675
- const bootstrapUrl = normalizeHttpUrl("authBaseUrl", input.bootstrapAuthBaseUrl);
3676
- if (input.runtime !== "browser") return bootstrapUrl;
3677
- try {
3678
- const localAuthBase = normalizeHttpUrl("authBaseUrl", `${derivedBrowserOrigin(input.runtime)}/api/auth`);
3679
- const remoteAuthBase = bootstrapUrl.endsWith("/api/auth") ? bootstrapUrl : `${bootstrapUrl}/api/auth`;
3680
- if (new URL(remoteAuthBase).host !== new URL(localAuthBase).host) return localAuthBase;
3681
- return bootstrapUrl;
3682
- } catch {
3683
- return bootstrapUrl;
3684
- }
3685
- }
3686
- function resolveRuntimeUrls(bootstrap, runtime, authBaseUrlOverride) {
3687
- try {
3688
- return {
3689
- ok: true,
3690
- value: {
3691
- authBaseUrl: resolveBrowserAuthBaseUrl({
3692
- bootstrapAuthBaseUrl: bootstrap.authBaseUrl,
3693
- runtime,
3694
- ...authBaseUrlOverride === void 0 ? {} : { override: authBaseUrlOverride }
3695
- }),
3696
- convexUrl: normalizeHttpUrl("convexUrl", bootstrap.convexUrl)
3697
- }
3698
- };
3699
- } catch (cause) {
3700
- return {
3701
- ok: false,
3702
- error: toPublicError(cause, "createProductionAdapters")
3703
- };
3704
- }
3705
- }
3706
- async function runBootstrap(effect) {
3707
- const result = await Effect.runPromise(Effect.result(effect));
3708
- if (Result.isSuccess(result)) return {
3709
- ok: true,
3710
- value: result.success
3711
- };
3712
- return {
3713
- ok: false,
3714
- error: toPublicError(result.failure, "bootstrap.resolve")
3715
- };
3716
- }
3717
- /**
3718
- * Bootstrap-event props. `runtime` used to be called `capxulEnv`, which was a
3719
- * name collision, not a value: it carried "browser"/"node", never an
3720
- * environment. ADR-0020 A1 makes `capxul_env` the environment discriminator
3721
- * every synced artifact filters on, so this field was renamed to what it
3722
- * actually is. `sdk_version` follows the canon envelope spelling.
3723
- */
3724
- function bootstrapTelemetryEnvelope(input, resolvedInput) {
3725
- return {
3726
- runtime: resolvedInput.runtime,
3727
- env: input.requirement ?? "none",
3728
- ...resolvedInput.origin === void 0 ? {} : { origin: resolvedInput.origin },
3729
- sdk_version: SDK_VERSION
3730
- };
3731
- }
3732
- async function emitBootstrapTelemetry(telemetry, event) {
3733
- if (telemetry === void 0) return;
3734
- await Effect.runPromise(telemetry.emit(event).pipe(Effect.catchDefect(() => Effect.void)));
3735
- }
3736
- function normalizeHttpUrl(field, raw) {
3737
- let parsed;
3738
- try {
3739
- parsed = new URL(raw);
3740
- } catch {
3741
- throw Errors.invalidInput(field, "must be an http or https URL");
3742
- }
3743
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
3744
- return parsed.toString().replace(/\/$/, "");
3745
- }
3746
- function toPublicError(cause, operation) {
3747
- if (cause instanceof CapxulError) return cause;
3748
- if (typeof cause === "object" && cause !== null) {
3749
- const publicError = cause.publicError;
3750
- if (publicError instanceof CapxulError) return publicError;
3751
- const nestedCause = cause.cause;
3752
- if (nestedCause instanceof CapxulError) return nestedCause;
3753
- }
3754
- return Errors.providerError("sdk-production-adapters", operation, cause);
3755
- }
3756
- function idempotentClose(close) {
3757
- let closed = false;
3758
- return async () => {
3759
- if (closed) return;
3760
- closed = true;
3761
- await close();
3762
- };
3763
- }
3764
- //#endregion
3765
340
  //#region src/surface/create-capxul-client-from-production.ts
3766
341
  /** Consumer-facing factory — accepts only production-meaningful inputs (#326). */
3767
342
  async function createCapxulClient(input) {
@@ -3886,7 +461,7 @@ function captureException(telemetry, error, context) {
3886
461
  for (const key of Object.keys(properties)) if (properties[key] === void 0) delete properties[key];
3887
462
  const props = projectSdkException({
3888
463
  properties,
3889
- sdkVersion: SDK_VERSION$1,
464
+ sdkVersion: SDK_VERSION,
3890
465
  operation,
3891
466
  errorKind: errorCode,
3892
467
  failureMode,
@@ -3921,4 +496,4 @@ function captureExceptionSync(telemetry, error, context) {
3921
496
  } catch {}
3922
497
  }
3923
498
  //#endregion
3924
- export { CAPXUL_ERROR_CODES, CAPXUL_OPERATIONS, CAPXUL_PAYMENTS_V2_ADDRESS, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, CHAIN_UPSTREAMS, CapxulError, EVM_ADDRESS_RE, Errors, FAILURE_MODES, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, captureException, captureExceptionSync, createCapxulClient, deriveDevPrivateKey, devPrivateKeySigner, eip1193AccountProvider, embeddedSigner, fingerprintPaymentIntent, formatMoney, inboxPhase, injectedWalletSigner, isCapxulError, isCapxulOperation, isClaimed, isMoneyParseError, isRestoring, isSettingUpLifecycle, localPrivateKeyAccountProvider, normalizeCapxulOperation, openfortEmbeddedSigner, openfortEmbeddedSignerFromWallet, openfortEmbeddedWalletPort, parseMoney, paymentPhase, postHogObservability, requestPhase, destination as resolveIdentityDestination, toCountryCode, toAddress as toEvmAddress, toHandle, toPartyId };
499
+ export { CAPXUL_ERROR_CODES, CAPXUL_OPERATIONS, CAPXUL_PAYMENTS_V2_ADDRESS, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, CHAIN_UPSTREAMS, CapxulError, EVM_ADDRESS_RE, Errors, FAILURE_MODES, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, PEG_RATES, assetSymbolFor, captureException, captureExceptionSync, createCapxulClient, deriveDevPrivateKey, devPrivateKeySigner, eip1193AccountProvider, embeddedSigner, fingerprintPaymentIntent, formatAssetAmount, formatMoney, inboxPhase, injectedWalletSigner, isCapxulError, isCapxulOperation, isClaimed, isMoneyParseError, isRestoring, isSettingUpLifecycle, localPrivateKeyAccountProvider, normalizeCapxulOperation, openfortEmbeddedSigner, openfortEmbeddedSignerFromWallet, openfortEmbeddedWalletPort, parseAssetAmount, parseMoney, paymentPhase, postHogObservability, requestPhase, destination as resolveIdentityDestination, toCountryCode, toAddress as toEvmAddress, toHandle, toPartyId, valueIn };