@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/README.md +5 -4
- package/dist/index.d.mts +149 -50
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +836 -183
- package/dist/index.mjs.map +1 -1
- package/dist/node/index.d.mts +1 -1
- package/dist/{signer-oaYGfjDe.d.mts → signer-3-awyCio.d.mts} +8 -5
- package/dist/{signer-oaYGfjDe.d.mts.map → signer-3-awyCio.d.mts.map} +1 -1
- package/package.json +4 -2
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
|
|
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:
|
|
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:
|
|
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
|
-
})
|
|
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:
|
|
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
|
|
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",
|
|
366
|
-
if (session === null)
|
|
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
|
-
|
|
409
|
-
|
|
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(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|
435
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
704
|
-
if (value === "idle")
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
770
|
-
|
|
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(
|
|
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(
|
|
888
|
-
return toCapxulResult(provisionSmartAccountProgram(
|
|
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
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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:
|
|
1503
|
+
error: Errors.invalidInput("account", "accountReadPort is required to resolve ready lifecycle")
|
|
1176
1504
|
};
|
|
1177
|
-
|
|
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:
|
|
1508
|
+
error: account.error
|
|
1184
1509
|
};
|
|
1185
|
-
|
|
1510
|
+
accountId = String(account.value.id);
|
|
1186
1511
|
}
|
|
1187
|
-
return
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
2369
|
-
*
|
|
2370
|
-
* `eth_sign`
|
|
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.
|
|
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
|
|
4533
|
-
if (!
|
|
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:
|
|
5154
|
+
reason: runtimeUrlsResult.error.code
|
|
4540
5155
|
}
|
|
4541
5156
|
});
|
|
4542
5157
|
await closeScope().catch(() => void 0);
|
|
4543
|
-
return
|
|
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
|
-
|
|
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
|
|
4605
|
-
|
|
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
|
-
...
|
|
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:
|
|
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
|
-
|
|
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
|