@capxul/sdk 0.2.0-alpha.5 → 1.0.0-alpha.6

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,15 +1,16 @@
1
1
  import { n as BrowserAuthCacheAdapter, o as AuthCachePortTag, t as InMemoryAuthCacheAdapter } from "./InMemoryAuthCacheAdapter-BK-B_ERB.mjs";
2
2
  import { Context, Data, Deferred, Duration, Effect, Either, Exit, Layer, Ref, Request, Scope } from "effect";
3
3
  import { Machine } from "@effect/experimental";
4
- import { CapxulError, Errors, USDX_CURRENCY, USDX_DECIMALS, compileOrgRoleDefinitions, decodeConvexError, isCapxulError, orgRoleKeyForLabel, orgRoleTemplateDefinitions } from "@capxul/config";
4
+ import { CapxulError, Errors, USDX_CURRENCY, USDX_DECIMALS, compileOrgRoleDefinitions, decodeConvexError, deriveCapxulSafeAddress, isCapxulError, orgRoleKeyForLabel, orgRoleTemplateDefinitions } from "@capxul/config";
5
5
  import { toAccountId, toAddress, toAllowedOrigin, toAnonymousDistinctId, toAppId, toAuthUserId, toChainId, toCountryCode, toCurrencyCode, toDurationMs, toEmail, toEpochMs, toEpochSeconds, toJwtToken, toKycTier, toOrgId, toPublishableKey, toPublishableKeyId, toRoleKey, toSessionToken, toSubAccountId } from "@capxul/types";
6
6
  import { redactTelemetryProps } from "@capxul/observability";
7
- import { formatUnits, isHex, keccak256, parseUnits } from "viem";
7
+ import { formatUnits, hexToBytes, isHex, keccak256, parseUnits, recoverAddress } from "viem";
8
8
  import { privateKeyToAccount } from "viem/accounts";
9
9
  import { Schema, TreeFormatter } from "@effect/schema";
10
10
  import { BootstrapEnvelope } from "@capxul/wire";
11
11
  import { ConvexClient } from "convex/browser";
12
12
  import { getFunctionName, makeFunctionReference } from "convex/server";
13
+ import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
13
14
  //#region src/ports/auth-client.ts
14
15
  var AuthClientError = class extends Data.TaggedError("AuthClientError") {};
15
16
  var AuthClientPortTag = class extends Context.Tag("@capxul/sdk/ports/AuthClientPort")() {};
@@ -27,9 +28,11 @@ var RequestOtpRequest = class extends Request.TaggedClass("RequestOtp") {};
27
28
  var VerifyOtpRequest = class extends Request.TaggedClass("VerifyOtp") {};
28
29
  var GetSessionRequest = class extends Request.TaggedClass("GetSession") {};
29
30
  var SignOutRequest = class extends Request.TaggedClass("SignOut") {};
31
+ var RestoreCachedSessionRequest = class extends Request.TaggedClass("RestoreCachedSession") {};
30
32
  var ResumeOtpEntryRequest = class extends Request.TaggedClass("ResumeOtpEntry") {};
31
33
  var ResetAuthFlowRequest = class extends Request.TaggedClass("ResetAuthFlow") {};
32
34
  var CommitAuthFlowRequest = class extends Request.TaggedClass("CommitAuthFlow") {};
35
+ const AUTH_FLOW_CONTEXT_REVISIONS = /* @__PURE__ */ new WeakMap();
33
36
  const DEFAULT_OTP_TTL_MS = 3e5;
34
37
  const ACTOR_MACHINE = /* @__PURE__ */ new WeakMap();
35
38
  const RUNTIME_CONTROLS = /* @__PURE__ */ new WeakMap();
@@ -38,6 +41,7 @@ const AUTH_FLOW_TRANSITION_TABLE = {
38
41
  valid: [
39
42
  "RequestOtp",
40
43
  "GetSession",
44
+ "RestoreCachedSession",
41
45
  "ResetAuthFlow"
42
46
  ],
43
47
  invalid: ["VerifyOtp WRONG_STATE", "SignOut WRONG_STATE"]
@@ -105,37 +109,40 @@ function publicInput(input) {
105
109
  return { otpTtlMs: input.otpTtlMs };
106
110
  }
107
111
  function initialContext() {
108
- return {
112
+ return withContextRevision({
109
113
  state: "idle",
110
114
  email: null,
111
115
  session: null,
112
116
  otpRequestedAt: null,
113
117
  error: null
114
- };
118
+ }, 0);
115
119
  }
116
120
  function createAuthFlowMachine() {
117
121
  return Machine.makeWith()((input) => {
118
122
  const normalized = normalizeInput(input);
119
123
  const bootInput = publicInput(normalized);
120
124
  const control = runtimeControl(input);
121
- return Machine.procedures.make(initialContext(), { identifier: `AuthFlow:${normalized.otpTtlMs}` }).pipe(Machine.procedures.addPrivate()("CommitAuthFlow", ({ request }) => Effect.succeed(tuple(tuple(request.input, request.context), request.context))), Machine.procedures.add()("RequestOtp", (context) => Effect.gen(function* () {
125
+ return Machine.procedures.make(initialContext(), { identifier: `AuthFlow:${normalized.otpTtlMs}` }).pipe(Machine.procedures.addPrivate()("CommitAuthFlow", ({ request, state }) => {
126
+ const next = mergeCommittedContext(state, request.context, request.policy);
127
+ return Effect.succeed(tuple(tuple(request.input, next), next));
128
+ }), Machine.procedures.add()("RequestOtp", (context) => Effect.gen(function* () {
122
129
  const email = assertEmailWellFormed(context.request.email);
123
130
  if (!canTransition(context.state.state, "RequestOtp")) return yield* Effect.fail(wrongState("RequestOtp", context.state.state));
124
131
  const anonDistinctId = context.state.anonDistinctId ?? makeAnonymousDistinctId();
125
- const transient = {
132
+ const transient = mutationContext(context.state, {
126
133
  state: "sending_otp",
127
134
  email,
128
135
  session: null,
129
136
  otpRequestedAt: null,
130
137
  anonDistinctId,
131
138
  error: null
132
- };
139
+ });
133
140
  return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, requestOtpCompletion(email, anonDistinctId, context.request.options), (failure) => failureContext(failure, {
134
141
  email: null,
135
142
  session: null,
136
143
  otpRequestedAt: null,
137
144
  anonDistinctId
138
- })).pipe(context.forkReplaceWith("auth-flow:request", transient), Effect.as(tuple(Machine.NoReply, transient)));
145
+ }), mutationCommitPolicy(transient)).pipe(context.forkReplaceWith("auth-flow:mutation", transient), Effect.as(tuple(Machine.NoReply, transient)));
139
146
  })), Machine.procedures.add()("VerifyOtp", (context) => Effect.gen(function* () {
140
147
  const email = assertEmailWellFormed(context.request.email);
141
148
  if (!/^\d{6}$/.test(context.request.otp)) return yield* Effect.fail(new AuthFlowError({
@@ -167,38 +174,51 @@ function createAuthFlowMachine() {
167
174
  });
168
175
  return yield* Effect.fail(failure);
169
176
  }
170
- const transient = {
177
+ const transient = mutationContext(context.state, {
171
178
  state: "verifying",
172
179
  email,
173
180
  session: null,
174
181
  otpRequestedAt: current.otpRequestedAt,
175
182
  anonDistinctId,
176
183
  error: null
177
- };
184
+ });
178
185
  return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, verifyOtpCompletion(email, context.request.otp, anonDistinctId, context.request.options), (failure) => failureContext(failure, {
179
186
  email,
180
187
  session: null,
181
188
  otpRequestedAt: current.otpRequestedAt,
182
189
  anonDistinctId
183
- })).pipe(context.forkReplaceWith("auth-flow:request", transient), Effect.as(tuple(Machine.NoReply, transient)));
190
+ }), mutationCommitPolicy(transient)).pipe(context.forkReplaceWith("auth-flow:mutation", transient), Effect.as(tuple(Machine.NoReply, transient)));
184
191
  })), Machine.procedures.add()("GetSession", (context) => Effect.gen(function* () {
185
192
  if (!canTransition(context.state.state, "GetSession")) return yield* Effect.fail(wrongState("GetSession", context.state.state));
186
- return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, getSessionCompletion(context.request.options), (failure) => failureContext(failure, {
193
+ return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, getSessionCompletion(context.request.options, context.state), (failure) => failureContext(failure, {
187
194
  email: context.state.email,
188
195
  session: context.state.session,
189
196
  otpRequestedAt: context.state.otpRequestedAt,
190
197
  ...context.state.anonDistinctId === void 0 ? {} : { anonDistinctId: context.state.anonDistinctId }
191
- })).pipe(context.forkReplaceWith("auth-flow:request", context.state), Effect.as(tuple(Machine.NoReply, context.state)));
198
+ }), {
199
+ _tag: "sessionRefresh",
200
+ startedRevision: contextRevision(context.state)
201
+ }).pipe(context.forkReplaceWith("auth-flow:session", context.state), Effect.as(tuple(Machine.NoReply, context.state)));
192
202
  })), Machine.procedures.add()("SignOut", (context) => Effect.gen(function* () {
193
203
  if (!canTransition(context.state.state, "SignOut")) return yield* Effect.fail(wrongState("SignOut", context.state.state));
194
- const transient = {
204
+ const transient = mutationContext(context.state, {
195
205
  state: "signing_out",
196
206
  email: context.state.email,
197
207
  session: context.state.session,
198
208
  otpRequestedAt: null,
199
209
  error: null
200
- };
201
- return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, signOutCompletion(context.request.options), () => initialContext()).pipe(context.forkReplaceWith("auth-flow:request", transient), Effect.as(tuple(Machine.NoReply, transient)));
210
+ });
211
+ return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, signOutCompletion(context.request.options), () => initialContext(), mutationCommitPolicy(transient)).pipe(context.forkReplaceWith("auth-flow:mutation", transient), Effect.as(tuple(Machine.NoReply, transient)));
212
+ })), Machine.procedures.add()("RestoreCachedSession", (context) => Effect.gen(function* () {
213
+ if (!canTransition(context.state.state, "RestoreCachedSession")) return yield* Effect.fail(wrongState("RestoreCachedSession", context.state.state));
214
+ const restored = mutationContext(context.state, {
215
+ state: "authenticated",
216
+ email: context.request.session.email,
217
+ session: context.request.session,
218
+ otpRequestedAt: null,
219
+ error: null
220
+ });
221
+ return tuple(tuple(bootInput, restored), restored);
202
222
  })), Machine.procedures.add()("ResumeOtpEntry", (context) => Effect.gen(function* () {
203
223
  if (!canTransition(context.state.state, "ResumeOtpEntry")) return yield* Effect.fail(wrongState("ResumeOtpEntry", context.state.state));
204
224
  const current = context.state;
@@ -223,17 +243,17 @@ function createAuthFlowMachine() {
223
243
  });
224
244
  return yield* Effect.fail(failure);
225
245
  }
226
- const restored = {
246
+ const restored = mutationContext(context.state, {
227
247
  state: "otp_requested",
228
248
  email: current.email,
229
249
  session: null,
230
250
  otpRequestedAt: current.otpRequestedAt,
231
251
  ...current.anonDistinctId === void 0 ? {} : { anonDistinctId: current.anonDistinctId },
232
252
  error: null
233
- };
253
+ });
234
254
  return tuple(tuple(bootInput, restored), restored);
235
- })), Machine.procedures.add()("ResetAuthFlow", () => {
236
- const reset = initialContext();
255
+ })), Machine.procedures.add()("ResetAuthFlow", (context) => {
256
+ const reset = mutationContext(context.state, initialContext());
237
257
  return Effect.succeed(tuple(tuple(bootInput, reset), reset));
238
258
  }));
239
259
  });
@@ -302,6 +322,7 @@ function toMachineRequest(request) {
302
322
  }, request.options));
303
323
  case "GetSession": return new GetSessionRequest(optionalOptions({}, request.options));
304
324
  case "SignOut": return new SignOutRequest(optionalOptions({}, request.options));
325
+ case "RestoreCachedSession": return new RestoreCachedSessionRequest({ session: request.session });
305
326
  case "ResumeOtpEntry": return new ResumeOtpEntryRequest();
306
327
  case "ResetAuthFlow": return new ResetAuthFlowRequest();
307
328
  }
@@ -360,10 +381,16 @@ function verifyOtpCompletion(email, otp, anonDistinctId, options) {
360
381
  };
361
382
  });
362
383
  }
363
- function getSessionCompletion(options) {
384
+ function isInFlightOtpAuthState(state) {
385
+ return state === "otp_requested" || state === "sending_otp" || state === "verifying";
386
+ }
387
+ function getSessionCompletion(options, current) {
364
388
  return Effect.gen(function* () {
365
- const session = yield* withAuthFlowTimeout((yield* AuthClientPortTag).getSession(authClientOptions(options)), "getSession", "idle", options).pipe(Effect.mapError((error) => authClientFailure("getSession", "idle", error)));
366
- if (session === null) return initialContext();
389
+ const session = yield* withAuthFlowTimeout((yield* AuthClientPortTag).getSession(authClientOptions(options)), "getSession", current.state, options).pipe(Effect.mapError((error) => authClientFailure("getSession", current.state, error)));
390
+ if (session === null) {
391
+ if (isInFlightOtpAuthState(current.state)) return current;
392
+ return initialContext();
393
+ }
367
394
  return {
368
395
  state: "authenticated",
369
396
  email: session.email,
@@ -402,21 +429,11 @@ function authClientFailure(operation, state, error) {
402
429
  if (error instanceof AuthFlowError) return error;
403
430
  return toAuthFlowError(operation, state, error);
404
431
  }
405
- function completeRequest(commit, deferred, input, control, effect, toFailureContext) {
432
+ function completeRequest(commit, deferred, input, control, effect, toFailureContext, policy) {
406
433
  return effect.pipe(Effect.either, Effect.flatMap((result) => {
407
434
  const stopped = stoppedFailure();
408
- if (control.stopped) return commit(new CommitAuthFlowRequest({
409
- input,
410
- context: toFailureContext(stopped)
411
- })).pipe(Effect.zipRight(Deferred.fail(deferred, stopped)));
412
- if (result._tag === "Right") return commit(new CommitAuthFlowRequest({
413
- input,
414
- context: result.right
415
- })).pipe(Effect.flatMap((snapshot) => Deferred.succeed(deferred, snapshot)));
416
- return commit(new CommitAuthFlowRequest({
417
- input,
418
- context: toFailureContext(result.left)
419
- })).pipe(Effect.zipRight(Deferred.fail(deferred, result.left)));
435
+ const resolution = control.stopped ? commit(commitAuthFlowRequest(input, toFailureContext(stopped), policy)).pipe(Effect.zipRight(Deferred.fail(deferred, stopped))) : result._tag === "Right" ? commit(commitAuthFlowRequest(input, result.right, policy)).pipe(Effect.flatMap((snapshot) => Deferred.succeed(deferred, snapshot))) : commit(commitAuthFlowRequest(input, toFailureContext(result.left), policy)).pipe(Effect.zipRight(Deferred.fail(deferred, result.left)));
436
+ return Effect.uninterruptible(resolution);
420
437
  }), Effect.catchAllDefect((cause) => {
421
438
  const failure = new AuthFlowError({
422
439
  operation: "transition",
@@ -425,14 +442,41 @@ function completeRequest(commit, deferred, input, control, effect, toFailureCont
425
442
  publicCode: "UNKNOWN",
426
443
  cause
427
444
  });
428
- return commit(new CommitAuthFlowRequest({
429
- input,
430
- context: toFailureContext(failure)
431
- })).pipe(Effect.zipRight(Deferred.fail(deferred, failure)));
432
- }), Effect.onInterrupt(() => commit(new CommitAuthFlowRequest({
445
+ return Effect.uninterruptible(commit(commitAuthFlowRequest(input, toFailureContext(failure), policy)).pipe(Effect.zipRight(Deferred.fail(deferred, failure))));
446
+ }), Effect.onInterrupt(() => Effect.uninterruptible(commit(commitAuthFlowRequest(input, toFailureContext(stoppedFailure()), policy)).pipe(Effect.zipRight(Deferred.fail(deferred, stoppedFailure()))))));
447
+ }
448
+ function commitAuthFlowRequest(input, context, policy) {
449
+ if (policy === void 0) return new CommitAuthFlowRequest({
433
450
  input,
434
- context: toFailureContext(stoppedFailure())
435
- })).pipe(Effect.zipRight(Deferred.fail(deferred, stoppedFailure())))));
451
+ context
452
+ });
453
+ return new CommitAuthFlowRequest({
454
+ input,
455
+ context,
456
+ policy
457
+ });
458
+ }
459
+ function mergeCommittedContext(current, next, policy) {
460
+ const currentRevision = contextRevision(current);
461
+ if (policy === void 0) return withContextRevision(next, currentRevision);
462
+ if (currentRevision !== policy.startedRevision) return current;
463
+ return withContextRevision(next, currentRevision);
464
+ }
465
+ function mutationCommitPolicy(context) {
466
+ return {
467
+ _tag: "mutation",
468
+ startedRevision: contextRevision(context)
469
+ };
470
+ }
471
+ function mutationContext(context, next) {
472
+ return withContextRevision(next, contextRevision(context) + 1);
473
+ }
474
+ function contextRevision(context) {
475
+ return AUTH_FLOW_CONTEXT_REVISIONS.get(context) ?? 0;
476
+ }
477
+ function withContextRevision(context, revision) {
478
+ AUTH_FLOW_CONTEXT_REVISIONS.set(context, revision);
479
+ return context;
436
480
  }
437
481
  function stoppedFailure() {
438
482
  return new AuthFlowError({
@@ -555,7 +599,16 @@ function hasMachineActor(actor) {
555
599
  const getSessionProgramWithOptions = (options) => Effect.gen(function* () {
556
600
  const deps = yield* CapxulDepsTag;
557
601
  const cached = yield* deps.authCache.getSession.pipe(Effect.mapError((e) => Errors.providerError("auth-cache", "getSession", e)));
558
- if (cached !== null) return cached;
602
+ const actorState = String(deps.actor.getSnapshot().value);
603
+ if (cached === null && (actorState === "otp_requested" || actorState === "sending_otp" || actorState === "verifying")) return null;
604
+ if (cached !== null && actorState !== "idle") return cached;
605
+ if (cached !== null && actorState === "idle") {
606
+ yield* sendAuthFlowRequest(deps.actor, {
607
+ _tag: "RestoreCachedSession",
608
+ session: cached
609
+ }).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
610
+ return cached;
611
+ }
559
612
  if (!hasMachineActor(deps.actor)) {
560
613
  const existingError = deps.actor.getSnapshot().context.error;
561
614
  if (existingError !== null) return yield* Effect.fail(existingError);
@@ -567,6 +620,7 @@ const getSessionProgramWithOptions = (options) => Effect.gen(function* () {
567
620
  if (refreshed[1].session !== null) return refreshed[1].session;
568
621
  const session = deps.actor.getSnapshot().context.session;
569
622
  if (session !== null) return session;
623
+ if (cached !== null) return cached;
570
624
  return null;
571
625
  });
572
626
  getSessionProgramWithOptions();
@@ -700,18 +754,28 @@ const signInProgram = (input, options) => Effect.gen(function* () {
700
754
  */
701
755
  const signOutProgramWithOptions = (options) => Effect.gen(function* () {
702
756
  const deps = yield* CapxulDepsTag;
703
- const value = String(deps.actor.getSnapshot().value);
704
- if (value === "idle") return yield* Effect.fail(Errors.wrongState({
705
- method: "signOut",
706
- currentState: value,
707
- validStates: [
708
- "authenticated",
709
- "otp_requested",
710
- "verifying",
711
- "sending_otp",
712
- "error"
713
- ]
714
- }));
757
+ let value = String(deps.actor.getSnapshot().value);
758
+ if (value === "idle") {
759
+ const cached = yield* deps.authCache.getSession.pipe(Effect.mapError((e) => Errors.providerError("auth-cache", "getSession", e)));
760
+ if (cached !== null) {
761
+ yield* sendAuthFlowRequest(deps.actor, {
762
+ _tag: "RestoreCachedSession",
763
+ session: cached
764
+ }).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
765
+ value = String(deps.actor.getSnapshot().value);
766
+ }
767
+ if (value === "idle") return yield* Effect.fail(Errors.wrongState({
768
+ method: "signOut",
769
+ currentState: value,
770
+ validStates: [
771
+ "authenticated",
772
+ "otp_requested",
773
+ "verifying",
774
+ "sending_otp",
775
+ "error"
776
+ ]
777
+ }));
778
+ }
715
779
  if (value === "error") {
716
780
  yield* sendAuthFlowRequest(deps.actor, {
717
781
  _tag: "ResetAuthFlow",
@@ -765,9 +829,20 @@ function makeAuthMethods(deps) {
765
829
  error: Errors.cancelled({ operation: "verifyOtp" })
766
830
  };
767
831
  const result = await toCapxulResult(verifyOtpProgram(input, options), capxulDepsLayer(deps));
768
- if (result.ok) {
769
- const afterVerifyOtp = deps.afterVerifyOtp;
770
- if (afterVerifyOtp !== void 0) Promise.resolve().then(() => afterVerifyOtp()).catch(() => void 0);
832
+ if (!result.ok) return result;
833
+ const afterVerifyOtp = deps.afterVerifyOtp;
834
+ if (afterVerifyOtp === void 0) return result;
835
+ try {
836
+ await afterVerifyOtp();
837
+ } catch (cause) {
838
+ if (cause instanceof CapxulError) return {
839
+ ok: false,
840
+ error: cause
841
+ };
842
+ return {
843
+ ok: false,
844
+ error: Errors.providerError("sdk", "afterVerifyOtp", cause)
845
+ };
771
846
  }
772
847
  return result;
773
848
  },
@@ -844,7 +919,7 @@ const loadSmartAccountProgram = Effect.gen(function* () {
844
919
  * port. The leading `signal.aborted` pre-check stays in the thin method
845
920
  * wrapper (provision currently takes no signal, so there is none).
846
921
  */
847
- function provisionSmartAccountProgram(input) {
922
+ function provisionSmartAccountProgram() {
848
923
  return Effect.gen(function* () {
849
924
  const deps = yield* SmartAccountDepsTag;
850
925
  const snap = deps.actor.getSnapshot();
@@ -854,19 +929,10 @@ function provisionSmartAccountProgram(input) {
854
929
  currentState: value,
855
930
  validStates: ["authenticated"]
856
931
  }));
857
- const signer = input?.externalSigner;
858
- if (signer === void 0) return yield* Effect.fail(Errors.invalidInput("externalSigner", "required for provisioning"));
859
- let signerAddress;
860
- try {
861
- signerAddress = toAddress(signer.address);
862
- } catch (err) {
863
- return yield* Effect.fail(err instanceof CapxulError ? err : Errors.invalidInput("externalSigner.address", "invalid EVM address"));
864
- }
865
932
  const session = snap.context.session;
866
933
  if (session === null) return yield* Effect.fail(Errors.notAuthenticated());
867
934
  const provisioned = yield* deps.smartAccountPort.provision({
868
935
  authUserId: session.authUserId,
869
- signerAddress,
870
936
  chainId: deps.chainId
871
937
  }).pipe(Effect.mapError((failure) => failure.publicError));
872
938
  yield* Effect.promise(() => emitProvisioningTelemetry(deps.telemetry, provisioned));
@@ -884,8 +950,8 @@ function makeSmartAccountMethods(deps) {
884
950
  };
885
951
  return toCapxulResult(loadSmartAccountProgram, smartAccountDepsLayer(deps));
886
952
  },
887
- async provision(input) {
888
- return toCapxulResult(provisionSmartAccountProgram(input), smartAccountDepsLayer(deps));
953
+ async provision(_input) {
954
+ return toCapxulResult(provisionSmartAccountProgram(), smartAccountDepsLayer(deps));
889
955
  }
890
956
  };
891
957
  }
@@ -1054,11 +1120,263 @@ function statusFromAccount(account, requirement) {
1054
1120
  };
1055
1121
  }
1056
1122
  //#endregion
1123
+ //#region src/client/account-lane.ts
1124
+ function isActiveProvisioningPhase(phase) {
1125
+ return phase.status === "wallet" || phase.status === "binding" || phase.status === "identity" || phase.status === "provision" || phase.status === "deploy";
1126
+ }
1127
+ function isRequirementMet(status, requirement) {
1128
+ if (requirement === "none") return status.status !== "notAuthenticated";
1129
+ if (status.status !== "accountReady") return false;
1130
+ if (requirement === "deployed") return status.deployment.status === "deployed";
1131
+ return true;
1132
+ }
1133
+ function createAccountLane(steps) {
1134
+ let phase = { status: "idle" };
1135
+ let inFlight = null;
1136
+ const setPhase = (next) => {
1137
+ phase = next;
1138
+ };
1139
+ const failAt = (at, error) => {
1140
+ const failed = {
1141
+ status: "failed",
1142
+ at,
1143
+ error
1144
+ };
1145
+ setPhase(failed);
1146
+ return failed;
1147
+ };
1148
+ const shouldAutoStart = async () => {
1149
+ if (steps.requirement === "none") return false;
1150
+ if (phase.status === "failed") return false;
1151
+ if (isActiveProvisioningPhase(phase)) return false;
1152
+ if (phase.status === "ready") return false;
1153
+ if (await steps.getSession() === null) return false;
1154
+ const status = await steps.getStatus();
1155
+ if (!status.ok) return false;
1156
+ return !isRequirementMet(status.value, steps.requirement);
1157
+ };
1158
+ const runLane = async () => {
1159
+ if (steps.requirement === "none") {
1160
+ setPhase({ status: "idle" });
1161
+ return phase;
1162
+ }
1163
+ const session = await steps.getSession();
1164
+ if (session === null) {
1165
+ setPhase({ status: "idle" });
1166
+ return phase;
1167
+ }
1168
+ let status = await steps.getStatus();
1169
+ if (!status.ok) return failAt("wallet", status.error);
1170
+ if (isRequirementMet(status.value, steps.requirement)) {
1171
+ setPhase({ status: "ready" });
1172
+ return phase;
1173
+ }
1174
+ if (status.value.status === "accountRequired" || status.value.status === "accountProviderReady" || status.value.status === "notAuthenticated") {
1175
+ setPhase({ status: "wallet" });
1176
+ const wallet = await steps.runWallet();
1177
+ if (!wallet.ok) return failAt("wallet", wallet.error);
1178
+ status = await steps.getStatus();
1179
+ if (!status.ok) return failAt("wallet", status.error);
1180
+ if (isRequirementMet(status.value, steps.requirement)) {
1181
+ setPhase({ status: "ready" });
1182
+ return phase;
1183
+ }
1184
+ }
1185
+ if (status.value.status === "accountProviderReady") {
1186
+ setPhase({ status: "binding" });
1187
+ const binding = await steps.runBinding(session);
1188
+ if (!binding.ok) return failAt("binding", binding.error);
1189
+ if (steps.requirement === "deployed") {
1190
+ setPhase({ status: "deploy" });
1191
+ const deployed = await steps.runDeploy();
1192
+ if (!deployed.ok) return failAt("deploy", deployed.error);
1193
+ } else {
1194
+ setPhase({ status: "identity" });
1195
+ const identity = await steps.runIdentity(session);
1196
+ if (!identity.ok) return failAt("identity", identity.error);
1197
+ setPhase({ status: "provision" });
1198
+ const provisioned = await steps.runProvision();
1199
+ if (!provisioned.ok) return failAt("provision", provisioned.error);
1200
+ }
1201
+ status = await steps.getStatus();
1202
+ if (!status.ok) return failAt(steps.requirement === "deployed" ? "deploy" : "provision", status.error);
1203
+ if (isRequirementMet(status.value, steps.requirement)) {
1204
+ setPhase({ status: "ready" });
1205
+ return phase;
1206
+ }
1207
+ }
1208
+ if (status.value.status === "accountPrepared" && steps.requirement === "deployed") {
1209
+ setPhase({ status: "deploy" });
1210
+ const deployed = await steps.runDeploy();
1211
+ if (!deployed.ok) return failAt("deploy", deployed.error);
1212
+ status = await steps.getStatus();
1213
+ if (!status.ok) return failAt("deploy", status.error);
1214
+ }
1215
+ if (status.ok && isRequirementMet(status.value, steps.requirement)) {
1216
+ setPhase({ status: "ready" });
1217
+ return phase;
1218
+ }
1219
+ setPhase({ status: "idle" });
1220
+ return phase;
1221
+ };
1222
+ const startLane = () => {
1223
+ if (inFlight !== null) return inFlight;
1224
+ inFlight = runLane().finally(() => {
1225
+ inFlight = null;
1226
+ });
1227
+ return inFlight;
1228
+ };
1229
+ return {
1230
+ getProvisioningPhase() {
1231
+ return phase;
1232
+ },
1233
+ kickProvisioning() {
1234
+ (async () => {
1235
+ if (await shouldAutoStart()) await startLane();
1236
+ })();
1237
+ },
1238
+ async resolveProvisioningPhase() {
1239
+ if (await shouldAutoStart()) return startLane();
1240
+ if (inFlight !== null) return inFlight;
1241
+ return phase;
1242
+ },
1243
+ async retryProvisioning() {
1244
+ if (inFlight !== null) return inFlight;
1245
+ if (phase.status === "failed" || phase.status === "idle") {
1246
+ setPhase({ status: "idle" });
1247
+ inFlight = runLane().finally(() => {
1248
+ inFlight = null;
1249
+ });
1250
+ return inFlight;
1251
+ }
1252
+ return phase;
1253
+ },
1254
+ async runLane() {
1255
+ if (inFlight !== null) return inFlight;
1256
+ inFlight = runLane().finally(() => {
1257
+ inFlight = null;
1258
+ });
1259
+ return inFlight;
1260
+ }
1261
+ };
1262
+ }
1263
+ //#endregion
1264
+ //#region src/client/account-lifecycle.ts
1265
+ function mapProvisioningFailureStep(step) {
1266
+ switch (step) {
1267
+ case "wallet": return "connecting";
1268
+ case "binding": return "confirmingIdentity";
1269
+ case "identity": return "registering";
1270
+ case "provision":
1271
+ case "deploy": return "activating";
1272
+ }
1273
+ }
1274
+ function mapProvisioningPhaseToSetupStep(phase) {
1275
+ switch (phase.status) {
1276
+ case "idle":
1277
+ case "wallet": return "connecting";
1278
+ case "binding": return "confirmingIdentity";
1279
+ case "identity": return "registering";
1280
+ case "provision":
1281
+ case "deploy": return "activating";
1282
+ case "ready":
1283
+ case "failed": return "connecting";
1284
+ }
1285
+ }
1286
+ function isSettingUpLifecycle(lifecycle) {
1287
+ return lifecycle.status === "settingUp";
1288
+ }
1289
+ function readyLifecycle(status, accountId) {
1290
+ return {
1291
+ status: "ready",
1292
+ accountId,
1293
+ canTransact: status.status === "accountReady" && status.deployment.status === "deployed"
1294
+ };
1295
+ }
1296
+ function mapAccountLifecycle(input) {
1297
+ const { status, phase, requirement, accountId } = input;
1298
+ if (status.status === "notAuthenticated") return { status: "loading" };
1299
+ if (phase.status === "failed") return {
1300
+ status: "failed",
1301
+ at: mapProvisioningFailureStep(phase.at),
1302
+ error: phase.error
1303
+ };
1304
+ if (isRequirementMet(status, requirement) || phase.status === "ready") {
1305
+ if (accountId === void 0) return { status: "loading" };
1306
+ return readyLifecycle(status, accountId);
1307
+ }
1308
+ if (isActiveProvisioningPhase(phase) || phase.status === "idle") return {
1309
+ status: "settingUp",
1310
+ step: mapProvisioningPhaseToSetupStep(phase)
1311
+ };
1312
+ return { status: "loading" };
1313
+ }
1314
+ //#endregion
1057
1315
  //#region src/client/account.ts
1058
1316
  function makeAccountMethods(deps) {
1059
1317
  toChainId(deps.chainId);
1060
1318
  const requirement = deps.requirement;
1061
1319
  const getStatus = () => toCapxulResult(accountStatusProgram, accountDepsLayer(deps));
1320
+ const runWallet = async () => {
1321
+ const signer = deps.signer;
1322
+ if (signer === void 0) return {
1323
+ ok: true,
1324
+ value: void 0
1325
+ };
1326
+ try {
1327
+ await signer.getAddress();
1328
+ return {
1329
+ ok: true,
1330
+ value: void 0
1331
+ };
1332
+ } catch (cause) {
1333
+ return {
1334
+ ok: false,
1335
+ error: Errors.providerError("signer", "getAddress", cause)
1336
+ };
1337
+ }
1338
+ };
1339
+ const alignBindingWithSigner = async (session) => {
1340
+ const bindingPort = deps.bindingPort;
1341
+ if (bindingPort === void 0) return {
1342
+ ok: true,
1343
+ value: void 0
1344
+ };
1345
+ const signer = deps.signer;
1346
+ let walletAddress;
1347
+ if (signer !== void 0) {
1348
+ const wallet = await runWallet();
1349
+ if (!wallet.ok) return wallet;
1350
+ try {
1351
+ walletAddress = toAddress(await signer.getAddress());
1352
+ } catch (cause) {
1353
+ return {
1354
+ ok: false,
1355
+ error: Errors.providerError("signer", "getAddress", cause)
1356
+ };
1357
+ }
1358
+ }
1359
+ const binding = await runPortEffect(bindingPort.ensureResolved({
1360
+ email: session.email,
1361
+ ...walletAddress === void 0 ? {} : { signerAddress: walletAddress }
1362
+ }));
1363
+ if (!binding.ok) return {
1364
+ ok: false,
1365
+ error: binding.error
1366
+ };
1367
+ if (walletAddress === void 0) return {
1368
+ ok: true,
1369
+ value: void 0
1370
+ };
1371
+ if (walletAddress.toLowerCase() !== String(binding.value.signerAddress).toLowerCase()) return {
1372
+ ok: false,
1373
+ error: Errors.invalidInput("signer", `embedded wallet EOA (${walletAddress}) does not match binding signer (${binding.value.signerAddress})`)
1374
+ };
1375
+ return {
1376
+ ok: true,
1377
+ value: void 0
1378
+ };
1379
+ };
1062
1380
  const ensureIdentityProfile = async (session) => {
1063
1381
  const existing = await runPortEffect(deps.identityPort.loadByAuthUserId(session.authUserId));
1064
1382
  if (!existing.ok) return {
@@ -1093,20 +1411,8 @@ function makeAccountMethods(deps) {
1093
1411
  ok: false,
1094
1412
  error: profileReady.error
1095
1413
  };
1096
- const signer = deps.signer;
1097
- if (signer === void 0) return {
1098
- ok: false,
1099
- error: Errors.smartAccountMissing("provision")
1100
- };
1101
- const signerAddressResult = await getSignerAddress(signer);
1102
- if (!signerAddressResult.ok) return {
1103
- ok: false,
1104
- error: signerAddressResult.error
1105
- };
1106
- const signerAddress = signerAddressResult.value;
1107
1414
  const provisioned = await runPortEffect(deps.smartAccountPort.provision({
1108
1415
  authUserId: session.authUserId,
1109
- signerAddress,
1110
1416
  chainId: toChainId(deps.chainId)
1111
1417
  }));
1112
1418
  if (!provisioned.ok) return provisioned;
@@ -1152,62 +1458,90 @@ function makeAccountMethods(deps) {
1152
1458
  deployMutex.set(mutexKey, work);
1153
1459
  return work;
1154
1460
  };
1461
+ const lane = createAccountLane({
1462
+ requirement,
1463
+ getStatus,
1464
+ getSession: () => currentSession(deps.actor, deps.authCache),
1465
+ runWallet,
1466
+ runBinding: alignBindingWithSigner,
1467
+ runIdentity: ensureIdentityProfile,
1468
+ runProvision: provisionImpl,
1469
+ runDeploy: deploySafeImpl
1470
+ });
1155
1471
  const ensureReady = async () => {
1472
+ const ran = await lane.runLane();
1473
+ if (ran.status === "failed") return {
1474
+ ok: false,
1475
+ error: ran.error
1476
+ };
1477
+ return getStatus();
1478
+ };
1479
+ const getProvisioningPhase = async () => {
1480
+ return {
1481
+ ok: true,
1482
+ value: await lane.resolveProvisioningPhase()
1483
+ };
1484
+ };
1485
+ const retryProvisioning = async () => {
1486
+ return {
1487
+ ok: true,
1488
+ value: await lane.retryProvisioning()
1489
+ };
1490
+ };
1491
+ const resolveLifecycle = async () => {
1492
+ const phase = await lane.resolveProvisioningPhase();
1156
1493
  const status = await getStatus();
1157
- if (!status.ok) return status;
1158
- if (status.value.status === "accountProviderReady" && requirement !== "deployed") {
1159
- const provisioned = await provisionImpl();
1160
- if (!provisioned.ok) return {
1161
- ok: false,
1162
- error: provisioned.error
1163
- };
1164
- return getStatus();
1165
- }
1166
- if (status.value.status === "accountProviderReady" && requirement === "deployed") {
1167
- const provisioned = await provisionImpl();
1168
- if (!provisioned.ok) return {
1169
- ok: false,
1170
- error: provisioned.error
1171
- };
1172
- const deployed = await deploySafeImpl();
1173
- if (!deployed.ok) return {
1494
+ if (!status.ok) return {
1495
+ ok: false,
1496
+ error: status.error
1497
+ };
1498
+ let accountId;
1499
+ if (status.value.status !== "notAuthenticated" && (phase.status === "ready" || isRequirementMet(status.value, requirement))) {
1500
+ const accountReadPort = deps.accountReadPort;
1501
+ if (accountReadPort === void 0) return {
1174
1502
  ok: false,
1175
- error: deployed.error
1503
+ error: Errors.invalidInput("account", "accountReadPort is required to resolve ready lifecycle")
1176
1504
  };
1177
- return getStatus();
1178
- }
1179
- if (status.value.status === "accountPrepared" && requirement === "deployed") {
1180
- const deployed = await deploySafeImpl();
1181
- if (!deployed.ok) return {
1505
+ const account = await runPortEffect(accountReadPort.readBalance({ chainId: toChainId(deps.chainId) }));
1506
+ if (!account.ok) return {
1182
1507
  ok: false,
1183
- error: deployed.error
1508
+ error: account.error
1184
1509
  };
1185
- return getStatus();
1510
+ accountId = String(account.value.id);
1186
1511
  }
1187
- return status;
1512
+ return {
1513
+ ok: true,
1514
+ value: mapAccountLifecycle({
1515
+ status: status.value,
1516
+ phase,
1517
+ requirement,
1518
+ ...accountId === void 0 ? {} : { accountId }
1519
+ })
1520
+ };
1521
+ };
1522
+ const getLifecycle = () => resolveLifecycle();
1523
+ const retrySetup = async () => {
1524
+ await lane.retryProvisioning();
1525
+ return resolveLifecycle();
1188
1526
  };
1189
1527
  return {
1190
- getStatus,
1191
- ensureReady,
1192
- _internal: {
1193
- provision: provisionImpl,
1194
- deploySafe: deploySafeImpl
1528
+ methods: {
1529
+ getLifecycle,
1530
+ retrySetup,
1531
+ _internal: {
1532
+ provision: provisionImpl,
1533
+ deploySafe: deploySafeImpl,
1534
+ getStatus,
1535
+ ensureReady,
1536
+ getProvisioningPhase,
1537
+ retryProvisioning
1538
+ }
1539
+ },
1540
+ kickProvisioning: () => {
1541
+ lane.kickProvisioning();
1195
1542
  }
1196
1543
  };
1197
1544
  }
1198
- async function getSignerAddress(signer) {
1199
- try {
1200
- return {
1201
- ok: true,
1202
- value: await signer.getAddress()
1203
- };
1204
- } catch (cause) {
1205
- return {
1206
- ok: false,
1207
- error: Errors.providerError("signer", "getAddress", cause)
1208
- };
1209
- }
1210
- }
1211
1545
  /**
1212
1546
  * Backend-orchestrated deploy (backend-orchestrated-deploy.md):
1213
1547
  * 1. `deployPrepare` — backend builds + gas-fills + paymaster-sponsors the
@@ -1248,6 +1582,27 @@ async function runBackendDeploy(input) {
1248
1582
  };
1249
1583
  }
1250
1584
  //#endregion
1585
+ //#region src/client/binding.ts
1586
+ function makeBindingMethods(deps) {
1587
+ return { async ensureResolved(input) {
1588
+ const port = deps.bindingPort;
1589
+ if (port === void 0) return {
1590
+ ok: false,
1591
+ error: Errors.invalidInput("binding", "binding port not configured")
1592
+ };
1593
+ let email;
1594
+ try {
1595
+ email = toEmail(input.email);
1596
+ } catch (cause) {
1597
+ return {
1598
+ ok: false,
1599
+ error: cause instanceof Error && "code" in cause ? cause : Errors.invalidInput("email", "invalid email")
1600
+ };
1601
+ }
1602
+ return runPortEffect(port.ensureResolved({ email }));
1603
+ } };
1604
+ }
1605
+ //#endregion
1251
1606
  //#region src/money/to-wei.ts
1252
1607
  /**
1253
1608
  * Lower consumer `Money` into an on-chain integer string for guarded writes.
@@ -2197,8 +2552,24 @@ function assembleCapxulClient(input) {
2197
2552
  const authFlowInput = input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs };
2198
2553
  const actor = Effect.runSync(bootAuthFlow(createAuthFlowMachine(), authFlowInput).pipe(Effect.provide(layer)));
2199
2554
  if (input.signal !== void 0) input.signal.addEventListener("abort", () => actor.stop(), { once: true });
2555
+ const binding = makeBindingMethods({ bindingPort: input.ports.binding });
2200
2556
  const selectedOrgPort = input.orgPort ?? input.orgDeploymentPort;
2201
2557
  let detectPendingOrgInvitations;
2558
+ let kickProvisioning;
2559
+ const accountBundle = makeAccountMethods({
2560
+ actor,
2561
+ authCache,
2562
+ identityPort: input.ports.identity,
2563
+ smartAccountPort: input.ports.smartAccount,
2564
+ telemetry: input.ports.telemetry,
2565
+ chainId: input.bootstrap.chainId,
2566
+ requirement: input.requirement,
2567
+ bindingPort: input.ports.binding,
2568
+ accountReadPort: input.ports.accountRead,
2569
+ ...input.signer === void 0 ? {} : { signer: input.signer }
2570
+ });
2571
+ kickProvisioning = accountBundle.kickProvisioning;
2572
+ const account = accountBundle.methods;
2202
2573
  const auth = makeAuthMethods({
2203
2574
  actor,
2204
2575
  authClient: input.ports.authClient,
@@ -2207,9 +2578,10 @@ function assembleCapxulClient(input) {
2207
2578
  telemetry: input.ports.telemetry,
2208
2579
  ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
2209
2580
  ...input.invokeTimeoutMs === void 0 ? {} : { invokeTimeoutMs: input.invokeTimeoutMs },
2210
- ...selectedOrgPort === void 0 ? {} : { afterVerifyOtp: async () => {
2581
+ afterVerifyOtp: async () => {
2211
2582
  await detectPendingOrgInvitations?.();
2212
- } }
2583
+ kickProvisioning?.();
2584
+ }
2213
2585
  });
2214
2586
  const smartAccount = makeSmartAccountMethods({
2215
2587
  actor,
@@ -2222,16 +2594,6 @@ function assembleCapxulClient(input) {
2222
2594
  identityPort: input.ports.identity,
2223
2595
  actor
2224
2596
  });
2225
- const account = makeAccountMethods({
2226
- actor,
2227
- authCache,
2228
- identityPort: input.ports.identity,
2229
- smartAccountPort: input.ports.smartAccount,
2230
- telemetry: input.ports.telemetry,
2231
- chainId: input.bootstrap.chainId,
2232
- requirement: input.requirement,
2233
- ...input.signer === void 0 ? {} : { signer: input.signer }
2234
- });
2235
2597
  const accounts = makeAccountsMethods({
2236
2598
  accountReadPort: input.ports.accountRead,
2237
2599
  chainId: input.bootstrap.chainId,
@@ -2256,6 +2618,7 @@ function assembleCapxulClient(input) {
2256
2618
  smartAccount,
2257
2619
  identity,
2258
2620
  account,
2621
+ binding,
2259
2622
  accounts,
2260
2623
  subAccounts,
2261
2624
  createOrg: orgMethods.createOrg,
@@ -2337,19 +2700,42 @@ function eip1193AccountProvider(input) {
2337
2700
  }
2338
2701
  };
2339
2702
  }
2340
- function openfortEmbeddedAccountProvider() {
2703
+ function openfortEmbeddedAccountProvider(input) {
2704
+ if (input.signer.source !== "openfort-embedded") throw Errors.invalidInput("signer.source", "must be \"openfort-embedded\"");
2705
+ let cachedAddress = null;
2706
+ let inFlight = null;
2707
+ const getAddress = async () => {
2708
+ if (cachedAddress !== null) return {
2709
+ ok: true,
2710
+ value: cachedAddress
2711
+ };
2712
+ if (inFlight !== null) return inFlight;
2713
+ inFlight = (async () => {
2714
+ try {
2715
+ const address = await input.signer.getAddress();
2716
+ cachedAddress = address;
2717
+ return {
2718
+ ok: true,
2719
+ value: address
2720
+ };
2721
+ } catch (err) {
2722
+ return {
2723
+ ok: false,
2724
+ error: Errors.providerError("openfort-embedded", "getAddress", err)
2725
+ };
2726
+ } finally {
2727
+ inFlight = null;
2728
+ }
2729
+ })();
2730
+ return inFlight;
2731
+ };
2341
2732
  return {
2342
2733
  source: "openfort-embedded",
2343
- async getAddress() {
2344
- return {
2345
- ok: false,
2346
- error: Errors.notImplemented("OpenfortAccountProvider", "getAddress")
2347
- };
2348
- },
2734
+ getAddress,
2349
2735
  async getDeployAccount() {
2350
2736
  return {
2351
2737
  ok: false,
2352
- error: Errors.notImplemented("OpenfortAccountProvider", "getDeployAccount")
2738
+ error: Errors.invalidInput("accountProvider", "openfort-embedded uses CapxulSigner + backend-orchestrated deploy; getDeployAccount is not supported")
2353
2739
  };
2354
2740
  }
2355
2741
  };
@@ -2361,26 +2747,29 @@ function firstAccount(value) {
2361
2747
  }
2362
2748
  //#endregion
2363
2749
  //#region src/signer.ts
2364
- const EVM_ADDRESS_HEX = /^0x[0-9a-fA-F]{40}$/;
2365
- const ECDSA_SIGNATURE_HEX = /^0x[0-9a-fA-F]{130}$/;
2750
+ const EVM_ADDRESS_HEX$1 = /^0x[0-9a-fA-F]{40}$/;
2751
+ const ECDSA_SIGNATURE_HEX$1 = /^0x[0-9a-fA-F]{130}$/;
2752
+ const SAFE_OP_DIGEST_HEX$1 = /^0x[0-9a-fA-F]{64}$/;
2366
2753
  /**
2367
2754
  * Browser `CapxulSigner` backed by an injected EIP-1193 wallet (MetaMask, etc.).
2368
- * Signs the SafeOp digest via `eth_sign` (raw-hash signing). The node key signer
2369
- * lives in `@capxul/sdk/node` (`localPrivateKeySigner`). Wallets that disable
2370
- * `eth_sign` must enable raw-hash signing before deployed-account flows run.
2755
+ * Signs the SafeOp digest via `eth_sign`, then verifies the returned signature
2756
+ * recovers the selected account against that raw digest. Wallets that prefix
2757
+ * `eth_sign` payloads are rejected before the backend submits an invalid SafeOp.
2758
+ * The node key signer lives in `@capxul/sdk/node` (`localPrivateKeySigner`).
2371
2759
  */
2372
2760
  function injectedWalletSigner(provider) {
2373
2761
  const resolveAddress = async () => {
2374
2762
  const accounts = await provider.request({ method: "eth_requestAccounts" });
2375
2763
  const first = Array.isArray(accounts) ? accounts[0] : void 0;
2376
2764
  if (typeof first !== "string") throw new Error("injectedWalletSigner: wallet returned no accounts");
2377
- if (!EVM_ADDRESS_HEX.test(first)) throw new Error("injectedWalletSigner: wallet returned invalid address format");
2765
+ if (!EVM_ADDRESS_HEX$1.test(first)) throw new Error("injectedWalletSigner: wallet returned invalid address format");
2378
2766
  return toAddress(first);
2379
2767
  };
2380
2768
  return {
2381
2769
  source: "injected-eip1193",
2382
2770
  getAddress: resolveAddress,
2383
2771
  async signUserOpHash(hash) {
2772
+ if (!SAFE_OP_DIGEST_HEX$1.test(hash)) throw new Error("injectedWalletSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
2384
2773
  const address = await resolveAddress();
2385
2774
  let signature;
2386
2775
  try {
@@ -2393,14 +2782,110 @@ function injectedWalletSigner(provider) {
2393
2782
  throw new Error(`injectedWalletSigner: eth_sign failed; enable raw-hash signing for deployment (${detail})`);
2394
2783
  }
2395
2784
  if (typeof signature !== "string") throw new Error("injectedWalletSigner: wallet returned a non-string signature");
2396
- if (!ECDSA_SIGNATURE_HEX.test(signature)) throw new Error("injectedWalletSigner: wallet returned invalid signature format");
2785
+ if (!ECDSA_SIGNATURE_HEX$1.test(signature)) throw new Error("injectedWalletSigner: wallet returned invalid signature format");
2786
+ if ((await recoverRawDigestSigner$1({
2787
+ hash,
2788
+ signature
2789
+ })).toLowerCase() !== address.toLowerCase()) throw new Error("injectedWalletSigner: wallet signature did not recover the selected account for the raw SafeOp digest; use a raw-hash-capable wallet or @capxul/sdk/node localPrivateKeySigner for deployed flows");
2397
2790
  return signature;
2398
2791
  }
2399
2792
  };
2400
2793
  }
2794
+ async function recoverRawDigestSigner$1(input) {
2795
+ try {
2796
+ return toAddress(await recoverAddress(input));
2797
+ } catch (cause) {
2798
+ const detail = cause instanceof Error ? cause.message : String(cause);
2799
+ throw new Error(`injectedWalletSigner: could not verify raw SafeOp digest signature (${detail})`);
2800
+ }
2801
+ }
2802
+ //#endregion
2803
+ //#region src/adapters/openfort/embedded-wallet-port.ts
2804
+ function openfortEmbeddedWalletPort(input) {
2805
+ return {
2806
+ async getAddress() {
2807
+ if (input.ensureReady !== void 0) await input.ensureReady();
2808
+ return (await input.embeddedWallet.get()).address;
2809
+ },
2810
+ async signRawDigest(hash) {
2811
+ if (input.ensureReady !== void 0) await input.ensureReady();
2812
+ const digestBytes = hexToBytes(hash, { size: 32 });
2813
+ return await input.embeddedWallet.signMessage(digestBytes, {
2814
+ hashMessage: false,
2815
+ arrayifyMessage: false
2816
+ });
2817
+ }
2818
+ };
2819
+ }
2820
+ //#endregion
2821
+ //#region src/openfort-embedded-signer.ts
2822
+ const EVM_ADDRESS_HEX = /^0x[0-9a-fA-F]{40}$/;
2823
+ const ECDSA_SIGNATURE_HEX = /^0x[0-9a-fA-F]{130}$/;
2824
+ const SAFE_OP_DIGEST_HEX = /^0x[0-9a-fA-F]{64}$/;
2825
+ /** Browser helper: wrap an initialized Openfort `embeddedWallet` API. */
2826
+ function openfortEmbeddedSignerFromWallet(input) {
2827
+ return openfortEmbeddedSigner({ wallet: openfortEmbeddedWalletPort({
2828
+ embeddedWallet: input.embeddedWallet,
2829
+ ...input.ensureWalletReady === void 0 ? {} : { ensureReady: input.ensureWalletReady }
2830
+ }) });
2831
+ }
2832
+ /**
2833
+ * Browser `CapxulSigner` backed by an Openfort embedded wallet (#335).
2834
+ * Signs the backend's SafeOp digest via raw `signMessage` (no EIP-191 prefix)
2835
+ * and verifies recovery before the backend submits.
2836
+ */
2837
+ function openfortEmbeddedSigner(input) {
2838
+ let cachedAddress = null;
2839
+ let addressInFlight = null;
2840
+ const resolveAddress = async () => {
2841
+ if (cachedAddress !== null) return cachedAddress;
2842
+ if (addressInFlight !== null) return addressInFlight;
2843
+ addressInFlight = (async () => {
2844
+ try {
2845
+ const raw = await input.wallet.getAddress();
2846
+ if (!EVM_ADDRESS_HEX.test(raw)) throw new Error("openfortEmbeddedSigner: embedded wallet returned invalid address format");
2847
+ const address = toAddress(raw);
2848
+ cachedAddress = address;
2849
+ return address;
2850
+ } finally {
2851
+ addressInFlight = null;
2852
+ }
2853
+ })();
2854
+ return addressInFlight;
2855
+ };
2856
+ return {
2857
+ source: "openfort-embedded",
2858
+ getAddress: resolveAddress,
2859
+ async signUserOpHash(hash) {
2860
+ if (!SAFE_OP_DIGEST_HEX.test(hash)) throw new Error("openfortEmbeddedSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
2861
+ const address = await resolveAddress();
2862
+ let signature;
2863
+ try {
2864
+ signature = await input.wallet.signRawDigest(hash);
2865
+ } catch (cause) {
2866
+ const detail = cause instanceof Error ? cause.message : String(cause);
2867
+ throw new Error(`openfortEmbeddedSigner: raw digest signing failed; ensure the embedded wallet is configured (${detail})`);
2868
+ }
2869
+ if (!ECDSA_SIGNATURE_HEX.test(signature)) throw new Error("openfortEmbeddedSigner: embedded wallet returned invalid signature format");
2870
+ if ((await recoverRawDigestSigner({
2871
+ hash,
2872
+ signature
2873
+ })).toLowerCase() !== address.toLowerCase()) throw new Error("openfortEmbeddedSigner: signature did not recover the embedded wallet address for the raw SafeOp digest");
2874
+ return signature;
2875
+ }
2876
+ };
2877
+ }
2878
+ async function recoverRawDigestSigner(input) {
2879
+ try {
2880
+ return toAddress(await recoverAddress(input));
2881
+ } catch (cause) {
2882
+ const detail = cause instanceof Error ? cause.message : String(cause);
2883
+ throw new Error(`openfortEmbeddedSigner: could not verify raw SafeOp digest signature (${detail})`);
2884
+ }
2885
+ }
2401
2886
  //#endregion
2402
2887
  //#region package.json
2403
- var version = "0.2.0-alpha.5";
2888
+ var version = "1.0.0-alpha.6";
2404
2889
  //#endregion
2405
2890
  //#region src/adapters/auth-client/effect-port.ts
2406
2891
  function authClientPortFromPromiseAdapter(adapter) {
@@ -2501,7 +2986,7 @@ var BetterAuthBrowserAdapter = class {
2501
2986
  fetchImpl;
2502
2987
  constructor(deps) {
2503
2988
  this.authBaseUrl = deps.authBaseUrl.replace(/\/$/, "");
2504
- this.fetchImpl = deps.fetch ?? fetch;
2989
+ this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
2505
2990
  }
2506
2991
  url(path) {
2507
2992
  return resolveAuthClientUrl(this.authBaseUrl, path);
@@ -3184,7 +3669,7 @@ var HttpBootstrapAdapter = class {
3184
3669
  fetchImpl;
3185
3670
  constructor(deps) {
3186
3671
  this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
3187
- this.fetchImpl = deps.fetch ?? fetch;
3672
+ this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
3188
3673
  }
3189
3674
  resolve(input) {
3190
3675
  return Effect.tryPromise({
@@ -3216,7 +3701,10 @@ var HttpBootstrapAdapter = class {
3216
3701
  issuedAt: state.issuedAt,
3217
3702
  expiresIn: state.expiresIn,
3218
3703
  authBaseUrl: normalizeRuntimeUrl("authBaseUrl", state.authBaseUrl),
3219
- convexUrl: normalizeRuntimeUrl("convexUrl", state.convexUrl)
3704
+ convexUrl: normalizeRuntimeUrl("convexUrl", state.convexUrl),
3705
+ siteBaseUrl: normalizeRuntimeUrl("siteBaseUrl", state.siteBaseUrl),
3706
+ openfortPublishableKey: state.openfortPublishableKey,
3707
+ shieldPublishableKey: state.shieldPublishableKey
3220
3708
  };
3221
3709
  },
3222
3710
  catch: (cause) => {
@@ -4082,6 +4570,52 @@ function subAccountErrorFromUnknown(operation, cause) {
4082
4570
  return subAccountErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
4083
4571
  }
4084
4572
  //#endregion
4573
+ //#region src/ports/binding.ts
4574
+ var BindingError = class extends Data.TaggedError("BindingError") {};
4575
+ function bindingErrorFromCapxul(operation, error, cause = error) {
4576
+ return new BindingError({
4577
+ operation,
4578
+ publicCode: error.code,
4579
+ publicError: error,
4580
+ cause,
4581
+ ...error.details === void 0 ? {} : { details: error.details }
4582
+ });
4583
+ }
4584
+ var BindingPortTag = class extends Context.Tag("@capxul/sdk/BindingPort")() {};
4585
+ //#endregion
4586
+ //#region src/adapters/binding/ConvexBindingAdapter.ts
4587
+ const resolveEmailBindingAction = makeFunctionReference("binding/actions:resolveEmailBinding");
4588
+ function brandBinding(row, email) {
4589
+ const expectedSafe = deriveCapxulSafeAddress({
4590
+ signerAddress: row.signerAddress,
4591
+ email
4592
+ }).toLowerCase();
4593
+ if (row.safeAddress.toLowerCase() !== expectedSafe) throw Errors.invalidInput("safeAddress", "binding safe does not match email derivation");
4594
+ return {
4595
+ bindingId: row.bindingId,
4596
+ signerAddress: toAddress(row.signerAddress),
4597
+ safeAddress: toAddress(row.safeAddress)
4598
+ };
4599
+ }
4600
+ var ConvexBindingAdapter = class {
4601
+ #convex;
4602
+ constructor(deps) {
4603
+ this.#convex = deps.convex;
4604
+ }
4605
+ ensureResolved(input) {
4606
+ return this.#convex.action(resolveEmailBindingAction, {
4607
+ email: input.email,
4608
+ ...input.signerAddress === void 0 ? {} : { signerAddress: String(input.signerAddress) }
4609
+ }).pipe(Effect.mapError((error) => bindingErrorFromCapxul("ensureResolved", error.publicError, error)), Effect.flatMap((row) => Effect.try({
4610
+ try: () => brandBinding(row, input.email),
4611
+ catch: (cause) => bindingErrorFromCapxul("ensureResolved", cause instanceof Error && "code" in cause ? cause : Errors.providerError("binding", "brand", cause), cause)
4612
+ })));
4613
+ }
4614
+ };
4615
+ const ConvexBindingLayer = () => Layer.effect(BindingPortTag, Effect.gen(function* () {
4616
+ return new ConvexBindingAdapter({ convex: yield* ConvexCallPortTag });
4617
+ }));
4618
+ //#endregion
4085
4619
  //#region src/ports/smart-account.ts
4086
4620
  var SmartAccountError = class extends Data.TaggedError("SmartAccountError") {};
4087
4621
  function smartAccountErrorFromCapxul(operation, error, cause = error) {
@@ -4118,10 +4652,7 @@ var ConvexSmartAccountAdapter = class {
4118
4652
  return this.#convex.query(this.#fns.loadBySmartAccountAddress, { address }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("loadBySmartAccountAddress", error.publicError, error)), Effect.flatMap((row) => brandSmartAccountEffect("loadBySmartAccountAddress", row)), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("loadBySmartAccountAddress", cause))));
4119
4653
  }
4120
4654
  provision(input) {
4121
- return this.#convex.mutation(this.#fns.provision, {
4122
- signerAddress: input.signerAddress,
4123
- chainId: wireChainId(input.chainId)
4124
- }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("provision", error.publicError, error)), Effect.flatMap((row) => Effect.try({
4655
+ 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({
4125
4656
  try: () => brandProvisionedSmartAccount(input.authUserId, row),
4126
4657
  catch: (cause) => smartAccountErrorFromUnknown("provision", cause)
4127
4658
  })), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("provision", cause))));
@@ -4342,6 +4873,85 @@ function asRecordArgs(args) {
4342
4873
  function transportErrorFromThrown(operation, request, cause) {
4343
4874
  return transportErrorFromCapxul(operation, request, cause instanceof Error ? Errors.providerError("transport", request.name, cause) : Errors.providerError("transport", request.name, new Error(String(cause))), cause);
4344
4875
  }
4876
+ //#endregion
4877
+ //#region src/openfort/create-openfort-browser-signer.ts
4878
+ function createOpenfortBrowserSignerFromBootstrap(bootstrap) {
4879
+ const authBaseUrl = normalizeBetterAuthBaseUrl(bootstrap.authBaseUrl);
4880
+ function betterAuthSessionUrl() {
4881
+ return `${authBaseUrl}/get-session`;
4882
+ }
4883
+ function encryptionSessionUrl() {
4884
+ return `${authBaseUrl}/encryption-session`;
4885
+ }
4886
+ async function fetchBetterAuthAccessToken() {
4887
+ const response = await fetch(betterAuthSessionUrl(), { credentials: "include" });
4888
+ if (!response.ok) return null;
4889
+ const token = (await response.json()).session?.token?.trim();
4890
+ return token !== void 0 && token.length > 0 ? token : null;
4891
+ }
4892
+ const openfort = new Openfort({
4893
+ baseConfiguration: { publishableKey: bootstrap.openfortPublishableKey },
4894
+ shieldConfiguration: { shieldPublishableKey: bootstrap.shieldPublishableKey },
4895
+ thirdPartyAuth: {
4896
+ provider: ThirdPartyOAuthProvider.BETTER_AUTH,
4897
+ getAccessToken: fetchBetterAuthAccessToken
4898
+ }
4899
+ });
4900
+ let walletReadyPromise = null;
4901
+ function startWalletReady() {
4902
+ return (async () => {
4903
+ await openfort.waitForInitialization();
4904
+ const accessToken = await fetchBetterAuthAccessToken();
4905
+ if (accessToken === null) throw Errors.notAuthenticated();
4906
+ const encryptionResponse = await fetch(encryptionSessionUrl(), {
4907
+ method: "POST",
4908
+ credentials: "include",
4909
+ headers: {
4910
+ Authorization: `Bearer ${accessToken}`,
4911
+ "Content-Type": "application/json"
4912
+ },
4913
+ body: JSON.stringify({})
4914
+ });
4915
+ if (!encryptionResponse.ok) {
4916
+ const detail = await encryptionResponse.text();
4917
+ throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error(`Openfort encryption session failed (${encryptionResponse.status}): ${detail.slice(0, 200)}`));
4918
+ }
4919
+ const encryptionBody = await encryptionResponse.json();
4920
+ if (typeof encryptionBody.sessionId !== "string" || encryptionBody.sessionId.length === 0) throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error("Openfort encryption session response missing sessionId"));
4921
+ if (await openfort.embeddedWallet.getEmbeddedState() !== EmbeddedState.READY) await openfort.embeddedWallet.configure({
4922
+ accountType: AccountTypeEnum.EOA,
4923
+ chainType: ChainTypeEnum.EVM,
4924
+ recoveryParams: {
4925
+ recoveryMethod: RecoveryMethod.AUTOMATIC,
4926
+ encryptionSession: encryptionBody.sessionId
4927
+ }
4928
+ });
4929
+ await openfort.embeddedWallet.get();
4930
+ })();
4931
+ }
4932
+ async function ensureOpenfortWalletReady() {
4933
+ walletReadyPromise ??= startWalletReady();
4934
+ try {
4935
+ await walletReadyPromise;
4936
+ } catch (cause) {
4937
+ walletReadyPromise = null;
4938
+ throw cause;
4939
+ }
4940
+ }
4941
+ return {
4942
+ ...openfortEmbeddedSignerFromWallet({
4943
+ embeddedWallet: openfort.embeddedWallet,
4944
+ ensureWalletReady: ensureOpenfortWalletReady
4945
+ }),
4946
+ resetSession: () => {
4947
+ walletReadyPromise = null;
4948
+ }
4949
+ };
4950
+ }
4951
+ function normalizeBetterAuthBaseUrl(raw) {
4952
+ const trimmed = raw.replace(/\/$/, "");
4953
+ return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
4954
+ }
4345
4955
  const SDK_VERSION = version;
4346
4956
  const collectProductionFlowPorts = Effect.gen(function* () {
4347
4957
  const authClient = yield* AuthClientPortTag;
@@ -4357,6 +4967,7 @@ const collectProductionFlowPorts = Effect.gen(function* () {
4357
4967
  authCache,
4358
4968
  identity: yield* IdentityPortTag,
4359
4969
  smartAccount: yield* SmartAccountPortTag,
4970
+ binding: yield* BindingPortTag,
4360
4971
  accountRead: yield* AccountReadPortTag,
4361
4972
  subAccount: yield* SubAccountPortTag,
4362
4973
  credentials,
@@ -4395,6 +5006,10 @@ function makeProductionAdapterLayerEntries(input) {
4395
5006
  name: "smartAccount",
4396
5007
  layer: ConvexSmartAccountLayer()
4397
5008
  },
5009
+ {
5010
+ name: "binding",
5011
+ layer: ConvexBindingLayer()
5012
+ },
4398
5013
  {
4399
5014
  name: "accountRead",
4400
5015
  layer: ConvexAccountLayer()
@@ -4447,7 +5062,7 @@ function mergeProductionAdapterLayers(entries) {
4447
5062
  }).reduce((current, layer) => Layer.merge(current, layer), Layer.empty);
4448
5063
  }
4449
5064
  function isConvexDependentLayer(name) {
4450
- return name === "identity" || name === "smartAccount" || name === "accountRead" || name === "subAccount" || name === "credentials" || name === "transport";
5065
+ return name === "identity" || name === "smartAccount" || name === "binding" || name === "accountRead" || name === "subAccount" || name === "credentials" || name === "transport";
4451
5066
  }
4452
5067
  function productionBootstrapPortLayer(bootstrap) {
4453
5068
  return Layer.succeed(BootstrapPortTag, bootstrap);
@@ -4529,19 +5144,20 @@ async function createProductionAdapters(input) {
4529
5144
  await closeScope().catch(() => void 0);
4530
5145
  return bootstrapResult;
4531
5146
  }
4532
- const runtimeUrls = resolveRuntimeUrls(bootstrapResult.value);
4533
- if (!runtimeUrls.ok) {
5147
+ const runtimeUrlsResult = resolveRuntimeUrls(bootstrapResult.value, input.authBaseUrl);
5148
+ if (!runtimeUrlsResult.ok) {
4534
5149
  await emitBootstrapTelemetry(input.telemetry, {
4535
5150
  name: "bootstrap_failed",
4536
5151
  props: {
4537
5152
  ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
4538
5153
  applicationId: bootstrapResult.value.applicationId,
4539
- reason: runtimeUrls.error.code
5154
+ reason: runtimeUrlsResult.error.code
4540
5155
  }
4541
5156
  });
4542
5157
  await closeScope().catch(() => void 0);
4543
- return runtimeUrls;
5158
+ return runtimeUrlsResult;
4544
5159
  }
5160
+ const runtimeUrls = runtimeUrlsResult;
4545
5161
  await emitBootstrapTelemetry(input.telemetry, {
4546
5162
  name: "bootstrap_resolved",
4547
5163
  props: {
@@ -4597,34 +5213,54 @@ function refreshConvexAuthOnSession(authClient, refresh) {
4597
5213
  signOut: (options) => authClient.signOut(options).pipe(Effect.tap(() => Effect.sync(refresh)))
4598
5214
  };
4599
5215
  }
4600
- async function createCapxulClient(input) {
5216
+ function wireOpenfortSignerLifecycle(client, signer) {
5217
+ if (signer === void 0 || !("resetSession" in signer)) return client;
5218
+ const { resetSession } = signer;
5219
+ const signOut = client.auth.signOut.bind(client.auth);
5220
+ return {
5221
+ ...client,
5222
+ auth: {
5223
+ ...client.auth,
5224
+ signOut: async (options) => {
5225
+ try {
5226
+ return await signOut(options);
5227
+ } finally {
5228
+ resetSession();
5229
+ }
5230
+ }
5231
+ }
5232
+ };
5233
+ }
5234
+ async function createCapxulClient$1(input) {
5235
+ const validation = validateCreateCapxulClientInput(input);
5236
+ if (!validation.ok) return validation;
4601
5237
  const adapters = await createProductionAdapters(input);
4602
5238
  if (!adapters.ok) return adapters;
4603
5239
  try {
4604
- const orgDeploymentPort = input.orgDeploymentPortFactory === void 0 ? void 0 : input.orgDeploymentPortFactory(adapters.value.ports.convexCall);
4605
- const client = assembleCapxulClient({
5240
+ const runtime = input.runtime ?? detectRuntime();
5241
+ let signer = input.signer;
5242
+ if (signer === void 0 && (input.requirement ?? "none") === "deployed" && runtime === "browser") signer = createOpenfortBrowserSignerFromBootstrap(adapters.value.bootstrap);
5243
+ const client = wireOpenfortSignerLifecycle(assembleCapxulClient({
4606
5244
  ports: adapters.value.ports,
4607
5245
  bootstrap: adapters.value.bootstrap,
4608
5246
  authCache: adapters.value.ports.authCache,
4609
5247
  requirement: input.requirement ?? "none",
4610
- ...input.signer === void 0 ? {} : { signer: input.signer },
4611
- ...orgDeploymentPort === void 0 ? {} : {
4612
- orgDeploymentPort,
4613
- orgRolesDeploymentPort: orgDeploymentPort,
4614
- orgSpendPort: orgDeploymentPort
4615
- },
4616
- ...input.orgDeploymentConfig === void 0 ? {} : { orgDeploymentConfig: input.orgDeploymentConfig },
5248
+ ...signer === void 0 ? {} : { signer },
4617
5249
  ...input.signal === void 0 ? {} : { signal: input.signal },
4618
5250
  ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
4619
5251
  invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4
4620
- });
5252
+ }), signer);
5253
+ const upstreamClose = adapters.value.close;
4621
5254
  return {
4622
5255
  ok: true,
4623
5256
  value: {
4624
5257
  ...client,
4625
5258
  _internal: {
4626
5259
  ...client._internal,
4627
- close: adapters.value.close
5260
+ close: async () => {
5261
+ if ("resetSession" in (signer ?? {})) signer.resetSession();
5262
+ await upstreamClose();
5263
+ }
4628
5264
  }
4629
5265
  }
4630
5266
  };
@@ -4636,6 +5272,17 @@ async function createCapxulClient(input) {
4636
5272
  };
4637
5273
  }
4638
5274
  }
5275
+ function validateCreateCapxulClientInput(input) {
5276
+ const runtime = input.runtime ?? detectRuntime();
5277
+ if ((input.requirement ?? "none") === "deployed" && input.signer === void 0 && runtime !== "browser") return {
5278
+ ok: false,
5279
+ error: Errors.invalidInput("signer", "required when requirement is \"deployed\"")
5280
+ };
5281
+ return {
5282
+ ok: true,
5283
+ value: void 0
5284
+ };
5285
+ }
4639
5286
  function resolveInput(input) {
4640
5287
  try {
4641
5288
  const runtime = input.runtime ?? detectRuntime();
@@ -4646,7 +5293,7 @@ function resolveInput(input) {
4646
5293
  value: {
4647
5294
  publishableKey,
4648
5295
  ...origin === void 0 ? {} : { origin },
4649
- bootstrapBaseUrl: normalizeHttpUrl("bootstrapBaseUrl", input.bootstrapBaseUrl ?? "https://api.capxul.com"),
5296
+ bootstrapBaseUrl: normalizeHttpUrl("bootstrapBaseUrl", input.bootstrapBaseUrl ?? (runtime === "browser" ? derivedBrowserOrigin(runtime) : "https://api.capxul.com")),
4650
5297
  runtime
4651
5298
  }
4652
5299
  };
@@ -4667,12 +5314,12 @@ function derivedBrowserOrigin(runtime) {
4667
5314
  if (typeof globalAny.location?.origin === "string" && globalAny.location.origin.length > 0) return globalAny.location.origin;
4668
5315
  throw Errors.invalidInput("origin", "required when browser location is unavailable");
4669
5316
  }
4670
- function resolveRuntimeUrls(bootstrap) {
5317
+ function resolveRuntimeUrls(bootstrap, authBaseUrlOverride) {
4671
5318
  try {
4672
5319
  return {
4673
5320
  ok: true,
4674
5321
  value: {
4675
- authBaseUrl: normalizeHttpUrl("authBaseUrl", bootstrap.authBaseUrl),
5322
+ authBaseUrl: normalizeHttpUrl("authBaseUrl", authBaseUrlOverride ?? bootstrap.authBaseUrl),
4676
5323
  convexUrl: normalizeHttpUrl("convexUrl", bootstrap.convexUrl)
4677
5324
  }
4678
5325
  };
@@ -4735,6 +5382,12 @@ function idempotentClose(close) {
4735
5382
  };
4736
5383
  }
4737
5384
  //#endregion
4738
- export { assembleCapxulClient, createCapxulClient, eip1193AccountProvider, injectedWalletSigner, localPrivateKeyAccountProvider, openfortEmbeddedAccountProvider };
5385
+ //#region src/client/create-capxul-client-from-production.ts
5386
+ /** Consumer-facing factory — accepts only production-meaningful inputs (#326). */
5387
+ async function createCapxulClient(input) {
5388
+ return createCapxulClient$1(input);
5389
+ }
5390
+ //#endregion
5391
+ export { assembleCapxulClient, createCapxulClient, eip1193AccountProvider, injectedWalletSigner, isSettingUpLifecycle, localPrivateKeyAccountProvider, openfortEmbeddedAccountProvider, openfortEmbeddedSigner, openfortEmbeddedSignerFromWallet, openfortEmbeddedWalletPort };
4739
5392
 
4740
5393
  //# sourceMappingURL=index.mjs.map