@capxul/sdk 0.2.0-alpha.3 → 0.2.0-alpha.5
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 +4 -266
- package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs +113 -0
- package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs.map +1 -0
- package/dist/index.d.mts +1263 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4740 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node/index.d.mts +55 -0
- package/dist/node/index.d.mts.map +1 -0
- package/dist/node/index.mjs +159 -0
- package/dist/node/index.mjs.map +1 -0
- package/dist/ports/safe-deployment.d.mts +2 -0
- package/dist/ports/safe-deployment.mjs +38 -0
- package/dist/ports/safe-deployment.mjs.map +1 -0
- package/dist/safe-deployment-Vni46k3t.d.mts +137 -0
- package/dist/safe-deployment-Vni46k3t.d.mts.map +1 -0
- package/dist/signer-oaYGfjDe.d.mts +142 -0
- package/dist/signer-oaYGfjDe.d.mts.map +1 -0
- package/package.json +37 -71
- package/CHANGELOG.md +0 -256
- package/LICENSE +0 -44
- package/dist/client-B_Z3ThFO.d.cts +0 -1484
- package/dist/client-pMBRFcsz.d.ts +0 -1484
- package/dist/client.cjs +0 -4324
- package/dist/client.d.cts +0 -6
- package/dist/client.d.ts +0 -6
- package/dist/client.js +0 -4322
- package/dist/errors-CwhCWGxm.d.ts +0 -70
- package/dist/errors-rqxuUhQP.d.cts +0 -70
- package/dist/errors.cjs +0 -35
- package/dist/errors.d.cts +0 -2
- package/dist/errors.d.ts +0 -2
- package/dist/errors.js +0 -31
- package/dist/index.cjs +0 -4626
- package/dist/index.d.cts +0 -571
- package/dist/index.d.ts +0 -571
- package/dist/index.js +0 -4585
- package/dist/next-action-CTGl8wpy.d.cts +0 -177
- package/dist/next-action-CTGl8wpy.d.ts +0 -177
- package/dist/types-Brucpq0Z.d.cts +0 -1191
- package/dist/types-V_D7qjxY.d.ts +0 -1191
- package/dist/webhooks.cjs +0 -118
- package/dist/webhooks.d.cts +0 -33
- package/dist/webhooks.d.ts +0 -33
- package/dist/webhooks.js +0 -116
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4740 @@
|
|
|
1
|
+
import { n as BrowserAuthCacheAdapter, o as AuthCachePortTag, t as InMemoryAuthCacheAdapter } from "./InMemoryAuthCacheAdapter-BK-B_ERB.mjs";
|
|
2
|
+
import { Context, Data, Deferred, Duration, Effect, Either, Exit, Layer, Ref, Request, Scope } from "effect";
|
|
3
|
+
import { Machine } from "@effect/experimental";
|
|
4
|
+
import { CapxulError, Errors, USDX_CURRENCY, USDX_DECIMALS, compileOrgRoleDefinitions, decodeConvexError, isCapxulError, orgRoleKeyForLabel, orgRoleTemplateDefinitions } from "@capxul/config";
|
|
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
|
+
import { redactTelemetryProps } from "@capxul/observability";
|
|
7
|
+
import { formatUnits, isHex, keccak256, parseUnits } from "viem";
|
|
8
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
9
|
+
import { Schema, TreeFormatter } from "@effect/schema";
|
|
10
|
+
import { BootstrapEnvelope } from "@capxul/wire";
|
|
11
|
+
import { ConvexClient } from "convex/browser";
|
|
12
|
+
import { getFunctionName, makeFunctionReference } from "convex/server";
|
|
13
|
+
//#region src/ports/auth-client.ts
|
|
14
|
+
var AuthClientError = class extends Data.TaggedError("AuthClientError") {};
|
|
15
|
+
var AuthClientPortTag = class extends Context.Tag("@capxul/sdk/ports/AuthClientPort")() {};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/ports/clock.ts
|
|
18
|
+
var ClockError = class extends Data.TaggedError("ClockError") {};
|
|
19
|
+
var ClockPortTag = class extends Context.Tag("@capxul/sdk/ports/ClockPort")() {};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/ports/telemetry.ts
|
|
22
|
+
var TelemetryPortTag = class extends Context.Tag("@capxul/sdk/ports/TelemetryPort")() {};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/flows/auth.ts
|
|
25
|
+
var AuthFlowError = class extends Data.TaggedError("AuthFlowError") {};
|
|
26
|
+
var RequestOtpRequest = class extends Request.TaggedClass("RequestOtp") {};
|
|
27
|
+
var VerifyOtpRequest = class extends Request.TaggedClass("VerifyOtp") {};
|
|
28
|
+
var GetSessionRequest = class extends Request.TaggedClass("GetSession") {};
|
|
29
|
+
var SignOutRequest = class extends Request.TaggedClass("SignOut") {};
|
|
30
|
+
var ResumeOtpEntryRequest = class extends Request.TaggedClass("ResumeOtpEntry") {};
|
|
31
|
+
var ResetAuthFlowRequest = class extends Request.TaggedClass("ResetAuthFlow") {};
|
|
32
|
+
var CommitAuthFlowRequest = class extends Request.TaggedClass("CommitAuthFlow") {};
|
|
33
|
+
const DEFAULT_OTP_TTL_MS = 3e5;
|
|
34
|
+
const ACTOR_MACHINE = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
const RUNTIME_CONTROLS = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
const AUTH_FLOW_TRANSITION_TABLE = {
|
|
37
|
+
idle: {
|
|
38
|
+
valid: [
|
|
39
|
+
"RequestOtp",
|
|
40
|
+
"GetSession",
|
|
41
|
+
"ResetAuthFlow"
|
|
42
|
+
],
|
|
43
|
+
invalid: ["VerifyOtp WRONG_STATE", "SignOut WRONG_STATE"]
|
|
44
|
+
},
|
|
45
|
+
sending_otp: {
|
|
46
|
+
valid: ["ResetAuthFlow", "SignOut"],
|
|
47
|
+
invalid: [
|
|
48
|
+
"RequestOtp WRONG_STATE",
|
|
49
|
+
"VerifyOtp WRONG_STATE",
|
|
50
|
+
"GetSession WRONG_STATE"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
otp_requested: {
|
|
54
|
+
valid: [
|
|
55
|
+
"RequestOtp",
|
|
56
|
+
"VerifyOtp",
|
|
57
|
+
"GetSession",
|
|
58
|
+
"SignOut",
|
|
59
|
+
"ResetAuthFlow"
|
|
60
|
+
],
|
|
61
|
+
invalid: []
|
|
62
|
+
},
|
|
63
|
+
verifying: {
|
|
64
|
+
valid: ["ResetAuthFlow", "SignOut"],
|
|
65
|
+
invalid: [
|
|
66
|
+
"RequestOtp WRONG_STATE",
|
|
67
|
+
"VerifyOtp WRONG_STATE",
|
|
68
|
+
"GetSession WRONG_STATE"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
authenticated: {
|
|
72
|
+
valid: [
|
|
73
|
+
"GetSession",
|
|
74
|
+
"SignOut",
|
|
75
|
+
"ResetAuthFlow"
|
|
76
|
+
],
|
|
77
|
+
invalid: ["RequestOtp WRONG_STATE", "VerifyOtp WRONG_STATE"]
|
|
78
|
+
},
|
|
79
|
+
signing_out: {
|
|
80
|
+
valid: ["ResetAuthFlow"],
|
|
81
|
+
invalid: [
|
|
82
|
+
"RequestOtp WRONG_STATE",
|
|
83
|
+
"VerifyOtp WRONG_STATE",
|
|
84
|
+
"GetSession WRONG_STATE",
|
|
85
|
+
"SignOut WRONG_STATE"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
error: {
|
|
89
|
+
valid: [
|
|
90
|
+
"ResetAuthFlow",
|
|
91
|
+
"RequestOtp",
|
|
92
|
+
"GetSession",
|
|
93
|
+
"ResumeOtpEntry"
|
|
94
|
+
],
|
|
95
|
+
invalid: ["VerifyOtp WRONG_STATE", "SignOut WRONG_STATE"]
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function tuple(first, second) {
|
|
99
|
+
return [first, second];
|
|
100
|
+
}
|
|
101
|
+
function normalizeInput(input) {
|
|
102
|
+
return { otpTtlMs: input.otpTtlMs ?? DEFAULT_OTP_TTL_MS };
|
|
103
|
+
}
|
|
104
|
+
function publicInput(input) {
|
|
105
|
+
return { otpTtlMs: input.otpTtlMs };
|
|
106
|
+
}
|
|
107
|
+
function initialContext() {
|
|
108
|
+
return {
|
|
109
|
+
state: "idle",
|
|
110
|
+
email: null,
|
|
111
|
+
session: null,
|
|
112
|
+
otpRequestedAt: null,
|
|
113
|
+
error: null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createAuthFlowMachine() {
|
|
117
|
+
return Machine.makeWith()((input) => {
|
|
118
|
+
const normalized = normalizeInput(input);
|
|
119
|
+
const bootInput = publicInput(normalized);
|
|
120
|
+
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* () {
|
|
122
|
+
const email = assertEmailWellFormed(context.request.email);
|
|
123
|
+
if (!canTransition(context.state.state, "RequestOtp")) return yield* Effect.fail(wrongState("RequestOtp", context.state.state));
|
|
124
|
+
const anonDistinctId = context.state.anonDistinctId ?? makeAnonymousDistinctId();
|
|
125
|
+
const transient = {
|
|
126
|
+
state: "sending_otp",
|
|
127
|
+
email,
|
|
128
|
+
session: null,
|
|
129
|
+
otpRequestedAt: null,
|
|
130
|
+
anonDistinctId,
|
|
131
|
+
error: null
|
|
132
|
+
};
|
|
133
|
+
return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, requestOtpCompletion(email, anonDistinctId, context.request.options), (failure) => failureContext(failure, {
|
|
134
|
+
email: null,
|
|
135
|
+
session: null,
|
|
136
|
+
otpRequestedAt: null,
|
|
137
|
+
anonDistinctId
|
|
138
|
+
})).pipe(context.forkReplaceWith("auth-flow:request", transient), Effect.as(tuple(Machine.NoReply, transient)));
|
|
139
|
+
})), Machine.procedures.add()("VerifyOtp", (context) => Effect.gen(function* () {
|
|
140
|
+
const email = assertEmailWellFormed(context.request.email);
|
|
141
|
+
if (!/^\d{6}$/.test(context.request.otp)) return yield* Effect.fail(new AuthFlowError({
|
|
142
|
+
operation: "verifyOtp",
|
|
143
|
+
state: context.state.state,
|
|
144
|
+
publicError: Errors.invalidInput("otp", "must be a six digit code"),
|
|
145
|
+
publicCode: "INVALID_INPUT"
|
|
146
|
+
}));
|
|
147
|
+
if (!canTransition(context.state.state, "VerifyOtp")) return yield* Effect.fail(wrongState("VerifyOtp", context.state.state));
|
|
148
|
+
const current = context.state;
|
|
149
|
+
const anonDistinctId = current.anonDistinctId ?? makeAnonymousDistinctId();
|
|
150
|
+
const now = yield* (yield* ClockPortTag).now.pipe(Effect.mapError((cause) => new AuthFlowError({
|
|
151
|
+
operation: "verifyOtp",
|
|
152
|
+
state: current.state,
|
|
153
|
+
publicError: Errors.providerError("clock", "now", cause),
|
|
154
|
+
publicCode: "PROVIDER_ERROR",
|
|
155
|
+
cause
|
|
156
|
+
})));
|
|
157
|
+
if (Number(now) - Number(current.otpRequestedAt) > normalized.otpTtlMs) {
|
|
158
|
+
const failure = new AuthFlowError({
|
|
159
|
+
operation: "verifyOtp",
|
|
160
|
+
state: "otp_requested",
|
|
161
|
+
publicError: Errors.otpExpired({ expiredAt: Number(current.otpRequestedAt) + normalized.otpTtlMs }),
|
|
162
|
+
publicCode: "OTP_EXPIRED"
|
|
163
|
+
});
|
|
164
|
+
yield* emitTelemetry({
|
|
165
|
+
name: "auth_otp_expired",
|
|
166
|
+
props: { email_domain: emailDomain$1(current.email ?? email) }
|
|
167
|
+
});
|
|
168
|
+
return yield* Effect.fail(failure);
|
|
169
|
+
}
|
|
170
|
+
const transient = {
|
|
171
|
+
state: "verifying",
|
|
172
|
+
email,
|
|
173
|
+
session: null,
|
|
174
|
+
otpRequestedAt: current.otpRequestedAt,
|
|
175
|
+
anonDistinctId,
|
|
176
|
+
error: null
|
|
177
|
+
};
|
|
178
|
+
return yield* completeRequest(context.unsafeSendAwait, context.deferred, bootInput, control, verifyOtpCompletion(email, context.request.otp, anonDistinctId, context.request.options), (failure) => failureContext(failure, {
|
|
179
|
+
email,
|
|
180
|
+
session: null,
|
|
181
|
+
otpRequestedAt: current.otpRequestedAt,
|
|
182
|
+
anonDistinctId
|
|
183
|
+
})).pipe(context.forkReplaceWith("auth-flow:request", transient), Effect.as(tuple(Machine.NoReply, transient)));
|
|
184
|
+
})), Machine.procedures.add()("GetSession", (context) => Effect.gen(function* () {
|
|
185
|
+
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, {
|
|
187
|
+
email: context.state.email,
|
|
188
|
+
session: context.state.session,
|
|
189
|
+
otpRequestedAt: context.state.otpRequestedAt,
|
|
190
|
+
...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)));
|
|
192
|
+
})), Machine.procedures.add()("SignOut", (context) => Effect.gen(function* () {
|
|
193
|
+
if (!canTransition(context.state.state, "SignOut")) return yield* Effect.fail(wrongState("SignOut", context.state.state));
|
|
194
|
+
const transient = {
|
|
195
|
+
state: "signing_out",
|
|
196
|
+
email: context.state.email,
|
|
197
|
+
session: context.state.session,
|
|
198
|
+
otpRequestedAt: null,
|
|
199
|
+
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)));
|
|
202
|
+
})), Machine.procedures.add()("ResumeOtpEntry", (context) => Effect.gen(function* () {
|
|
203
|
+
if (!canTransition(context.state.state, "ResumeOtpEntry")) return yield* Effect.fail(wrongState("ResumeOtpEntry", context.state.state));
|
|
204
|
+
const current = context.state;
|
|
205
|
+
if (current.email === null || current.otpRequestedAt === null) return yield* Effect.fail(wrongState("ResumeOtpEntry", context.state.state));
|
|
206
|
+
const now = yield* (yield* ClockPortTag).now.pipe(Effect.mapError((cause) => new AuthFlowError({
|
|
207
|
+
operation: "verifyOtp",
|
|
208
|
+
state: current.state,
|
|
209
|
+
publicError: Errors.providerError("clock", "now", cause),
|
|
210
|
+
publicCode: "PROVIDER_ERROR",
|
|
211
|
+
cause
|
|
212
|
+
})));
|
|
213
|
+
if (Number(now) - Number(current.otpRequestedAt) > normalized.otpTtlMs) {
|
|
214
|
+
const failure = new AuthFlowError({
|
|
215
|
+
operation: "verifyOtp",
|
|
216
|
+
state: "error",
|
|
217
|
+
publicError: Errors.otpExpired({ expiredAt: Number(current.otpRequestedAt) + normalized.otpTtlMs }),
|
|
218
|
+
publicCode: "OTP_EXPIRED"
|
|
219
|
+
});
|
|
220
|
+
yield* emitTelemetry({
|
|
221
|
+
name: "auth_otp_expired",
|
|
222
|
+
props: { email_domain: emailDomain$1(current.email) }
|
|
223
|
+
});
|
|
224
|
+
return yield* Effect.fail(failure);
|
|
225
|
+
}
|
|
226
|
+
const restored = {
|
|
227
|
+
state: "otp_requested",
|
|
228
|
+
email: current.email,
|
|
229
|
+
session: null,
|
|
230
|
+
otpRequestedAt: current.otpRequestedAt,
|
|
231
|
+
...current.anonDistinctId === void 0 ? {} : { anonDistinctId: current.anonDistinctId },
|
|
232
|
+
error: null
|
|
233
|
+
};
|
|
234
|
+
return tuple(tuple(bootInput, restored), restored);
|
|
235
|
+
})), Machine.procedures.add()("ResetAuthFlow", () => {
|
|
236
|
+
const reset = initialContext();
|
|
237
|
+
return Effect.succeed(tuple(tuple(bootInput, reset), reset));
|
|
238
|
+
}));
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function bootAuthFlow(machine, input) {
|
|
242
|
+
return Effect.gen(function* () {
|
|
243
|
+
const bootInput = publicInput(normalizeInput(input));
|
|
244
|
+
const scope = yield* Scope.make();
|
|
245
|
+
const control = { stopped: false };
|
|
246
|
+
const actor = yield* Machine.boot(machine, withRuntimeControl(bootInput, control)).pipe(Scope.extend(scope));
|
|
247
|
+
const statusRef = yield* Ref.make("active");
|
|
248
|
+
const stopDeferred = yield* Deferred.make();
|
|
249
|
+
const flowActor = {
|
|
250
|
+
input: bootInput,
|
|
251
|
+
getSnapshot() {
|
|
252
|
+
const context = Effect.runSync(actor.get);
|
|
253
|
+
const status = Effect.runSync(Ref.get(statusRef));
|
|
254
|
+
return {
|
|
255
|
+
value: context.state,
|
|
256
|
+
context,
|
|
257
|
+
status
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
stop() {
|
|
261
|
+
control.stopped = true;
|
|
262
|
+
Effect.runSync(Ref.set(statusRef, "stopped"));
|
|
263
|
+
Effect.runSync(Deferred.succeed(stopDeferred, void 0));
|
|
264
|
+
Effect.runFork(Scope.close(scope, Exit.void));
|
|
265
|
+
},
|
|
266
|
+
send(request) {
|
|
267
|
+
return Ref.get(statusRef).pipe(Effect.flatMap((status) => status === "active" ? actor.send(toMachineRequest(request)).pipe(Effect.raceFirst(Deferred.await(stopDeferred).pipe(Effect.flatMap(() => Effect.fail(new AuthFlowError({
|
|
268
|
+
operation: "transition",
|
|
269
|
+
state: Effect.runSync(actor.get).state,
|
|
270
|
+
publicError: Errors.cancelled({ operation: "authFlow.send" }),
|
|
271
|
+
publicCode: "CANCELLED"
|
|
272
|
+
})))))) : Effect.fail(new AuthFlowError({
|
|
273
|
+
operation: "transition",
|
|
274
|
+
state: Effect.runSync(actor.get).state,
|
|
275
|
+
publicError: Errors.cancelled({ operation: "authFlow.send" }),
|
|
276
|
+
publicCode: "CANCELLED"
|
|
277
|
+
}))), Effect.tapError((failure) => failure.publicCode === "OTP_EXPIRED" ? Effect.void : emitTelemetry({
|
|
278
|
+
name: "auth_failed",
|
|
279
|
+
props: {
|
|
280
|
+
auth_type: "otp",
|
|
281
|
+
reason: failure.publicCode
|
|
282
|
+
}
|
|
283
|
+
})));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
ACTOR_MACHINE.set(flowActor, actor);
|
|
287
|
+
return flowActor;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function sendAuthFlowRequest(actor, request) {
|
|
291
|
+
return actor.send(request);
|
|
292
|
+
}
|
|
293
|
+
function assertEmailWellFormed(email) {
|
|
294
|
+
return toEmail(email);
|
|
295
|
+
}
|
|
296
|
+
function toMachineRequest(request) {
|
|
297
|
+
switch (request._tag) {
|
|
298
|
+
case "RequestOtp": return new RequestOtpRequest(optionalOptions({ email: request.email }, request.options));
|
|
299
|
+
case "VerifyOtp": return new VerifyOtpRequest(optionalOptions({
|
|
300
|
+
email: request.email,
|
|
301
|
+
otp: request.otp
|
|
302
|
+
}, request.options));
|
|
303
|
+
case "GetSession": return new GetSessionRequest(optionalOptions({}, request.options));
|
|
304
|
+
case "SignOut": return new SignOutRequest(optionalOptions({}, request.options));
|
|
305
|
+
case "ResumeOtpEntry": return new ResumeOtpEntryRequest();
|
|
306
|
+
case "ResetAuthFlow": return new ResetAuthFlowRequest();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function optionalOptions(base, options) {
|
|
310
|
+
return options === void 0 ? base : {
|
|
311
|
+
...base,
|
|
312
|
+
options
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function requestOtpCompletion(email, anonDistinctId, options) {
|
|
316
|
+
return Effect.gen(function* () {
|
|
317
|
+
yield* withAuthFlowTimeout((yield* AuthClientPortTag).sendOtp({ email }, authClientOptions(options)), "requestOtp", "sending_otp", options).pipe(Effect.mapError((error) => authClientFailure("requestOtp", "sending_otp", error)));
|
|
318
|
+
const now = yield* (yield* ClockPortTag).now.pipe(Effect.mapError((cause) => new AuthFlowError({
|
|
319
|
+
operation: "requestOtp",
|
|
320
|
+
state: "sending_otp",
|
|
321
|
+
publicError: Errors.providerError("clock", "now", cause),
|
|
322
|
+
publicCode: "PROVIDER_ERROR",
|
|
323
|
+
cause
|
|
324
|
+
})));
|
|
325
|
+
yield* emitTelemetry({
|
|
326
|
+
name: "auth_otp_requested",
|
|
327
|
+
props: { email_domain: emailDomain$1(email) }
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
state: "otp_requested",
|
|
331
|
+
email,
|
|
332
|
+
session: null,
|
|
333
|
+
otpRequestedAt: now,
|
|
334
|
+
anonDistinctId,
|
|
335
|
+
error: null
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function verifyOtpCompletion(email, otp, anonDistinctId, options) {
|
|
340
|
+
return Effect.gen(function* () {
|
|
341
|
+
const session = yield* withAuthFlowTimeout((yield* AuthClientPortTag).verifyOtp({
|
|
342
|
+
email,
|
|
343
|
+
otp
|
|
344
|
+
}, authClientOptions(options)), "verifyOtp", "verifying", options).pipe(Effect.mapError((error) => authClientFailure("verifyOtp", "verifying", error)));
|
|
345
|
+
yield* emitTelemetry({
|
|
346
|
+
name: "auth_verified",
|
|
347
|
+
props: { auth_type: "otp" }
|
|
348
|
+
});
|
|
349
|
+
yield* identifyTelemetry({
|
|
350
|
+
distinctId: session.authUserId,
|
|
351
|
+
anonDistinctId,
|
|
352
|
+
traits: { email_domain: emailDomain$1(session.email) }
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
state: "authenticated",
|
|
356
|
+
email: session.email,
|
|
357
|
+
session,
|
|
358
|
+
otpRequestedAt: null,
|
|
359
|
+
error: null
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function getSessionCompletion(options) {
|
|
364
|
+
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();
|
|
367
|
+
return {
|
|
368
|
+
state: "authenticated",
|
|
369
|
+
email: session.email,
|
|
370
|
+
session,
|
|
371
|
+
otpRequestedAt: null,
|
|
372
|
+
error: null
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function signOutCompletion(options) {
|
|
377
|
+
return Effect.gen(function* () {
|
|
378
|
+
yield* withAuthFlowTimeout((yield* AuthClientPortTag).signOut(authClientOptions(options)), "signOut", "signing_out", options).pipe(Effect.mapError((error) => authClientFailure("signOut", "signing_out", error)));
|
|
379
|
+
yield* emitTelemetry({ name: "auth_signed_out" });
|
|
380
|
+
yield* resetTelemetry();
|
|
381
|
+
return initialContext();
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function withAuthFlowTimeout(effect, operation, state, options) {
|
|
385
|
+
const timeoutMs = options?.timeoutMs;
|
|
386
|
+
if (timeoutMs === void 0) return effect;
|
|
387
|
+
return effect.pipe(Effect.timeoutFail({
|
|
388
|
+
duration: Duration.millis(timeoutMs),
|
|
389
|
+
onTimeout: () => new AuthFlowError({
|
|
390
|
+
operation,
|
|
391
|
+
state,
|
|
392
|
+
publicError: Errors.providerTimeout("auth-flow", operation, timeoutMs),
|
|
393
|
+
publicCode: "PROVIDER_ERROR"
|
|
394
|
+
})
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
function authClientOptions(options) {
|
|
398
|
+
if (options?.signal === void 0) return void 0;
|
|
399
|
+
return { signal: options.signal };
|
|
400
|
+
}
|
|
401
|
+
function authClientFailure(operation, state, error) {
|
|
402
|
+
if (error instanceof AuthFlowError) return error;
|
|
403
|
+
return toAuthFlowError(operation, state, error);
|
|
404
|
+
}
|
|
405
|
+
function completeRequest(commit, deferred, input, control, effect, toFailureContext) {
|
|
406
|
+
return effect.pipe(Effect.either, Effect.flatMap((result) => {
|
|
407
|
+
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)));
|
|
420
|
+
}), Effect.catchAllDefect((cause) => {
|
|
421
|
+
const failure = new AuthFlowError({
|
|
422
|
+
operation: "transition",
|
|
423
|
+
state: "error",
|
|
424
|
+
publicError: Errors.unknown(cause),
|
|
425
|
+
publicCode: "UNKNOWN",
|
|
426
|
+
cause
|
|
427
|
+
});
|
|
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({
|
|
433
|
+
input,
|
|
434
|
+
context: toFailureContext(stoppedFailure())
|
|
435
|
+
})).pipe(Effect.zipRight(Deferred.fail(deferred, stoppedFailure())))));
|
|
436
|
+
}
|
|
437
|
+
function stoppedFailure() {
|
|
438
|
+
return new AuthFlowError({
|
|
439
|
+
operation: "transition",
|
|
440
|
+
state: "error",
|
|
441
|
+
publicError: Errors.cancelled({ operation: "authFlow.request" }),
|
|
442
|
+
publicCode: "CANCELLED"
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
function withRuntimeControl(input, control) {
|
|
446
|
+
const runtimeInput = { ...input };
|
|
447
|
+
RUNTIME_CONTROLS.set(runtimeInput, control);
|
|
448
|
+
return runtimeInput;
|
|
449
|
+
}
|
|
450
|
+
function runtimeControl(input) {
|
|
451
|
+
return RUNTIME_CONTROLS.get(input) ?? { stopped: false };
|
|
452
|
+
}
|
|
453
|
+
function failureContext(failure, preserved) {
|
|
454
|
+
return {
|
|
455
|
+
state: "error",
|
|
456
|
+
email: preserved.email,
|
|
457
|
+
session: preserved.session,
|
|
458
|
+
otpRequestedAt: preserved.otpRequestedAt,
|
|
459
|
+
...preserved.anonDistinctId === void 0 ? {} : { anonDistinctId: preserved.anonDistinctId },
|
|
460
|
+
error: failure.publicError
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function canTransition(state, request) {
|
|
464
|
+
return AUTH_FLOW_TRANSITION_TABLE[state].valid.includes(request);
|
|
465
|
+
}
|
|
466
|
+
function wrongState(request, state) {
|
|
467
|
+
return new AuthFlowError({
|
|
468
|
+
operation: "transition",
|
|
469
|
+
state,
|
|
470
|
+
publicError: Errors.wrongState({
|
|
471
|
+
method: request,
|
|
472
|
+
currentState: state,
|
|
473
|
+
validStates: AUTH_FLOW_TRANSITION_TABLE[state].valid
|
|
474
|
+
}),
|
|
475
|
+
publicCode: "WRONG_STATE"
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function toAuthFlowError(operation, state, error) {
|
|
479
|
+
const publicError = toCapxulError(operation, error);
|
|
480
|
+
return new AuthFlowError({
|
|
481
|
+
operation,
|
|
482
|
+
state,
|
|
483
|
+
publicError,
|
|
484
|
+
publicCode: publicError.code,
|
|
485
|
+
cause: error
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
function toCapxulError(operation, error) {
|
|
489
|
+
if (error.cause instanceof CapxulError) return error.cause;
|
|
490
|
+
if (operation === "requestOtp" && error.kind === "provider") return Errors.emailDeliveryFailed("provider rejected OTP request");
|
|
491
|
+
return Errors.providerError("auth-client", operation, /* @__PURE__ */ new Error("auth-client provider failure"));
|
|
492
|
+
}
|
|
493
|
+
function emitTelemetry(event) {
|
|
494
|
+
return Effect.gen(function* () {
|
|
495
|
+
yield* (yield* TelemetryPortTag).emit(event).pipe(Effect.catchAllDefect(() => Effect.void), Effect.catchAll(() => Effect.void));
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function identifyTelemetry(input) {
|
|
499
|
+
return Effect.gen(function* () {
|
|
500
|
+
yield* (yield* TelemetryPortTag).identify(input).pipe(Effect.catchAllDefect(() => Effect.void), Effect.catchAll(() => Effect.void));
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function resetTelemetry() {
|
|
504
|
+
return Effect.gen(function* () {
|
|
505
|
+
yield* (yield* TelemetryPortTag).reset().pipe(Effect.catchAllDefect(() => Effect.void), Effect.catchAll(() => Effect.void));
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function makeAnonymousDistinctId() {
|
|
509
|
+
return toAnonymousDistinctId(`anon_${globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}`);
|
|
510
|
+
}
|
|
511
|
+
function emailDomain$1(email) {
|
|
512
|
+
const [, domain = "unknown"] = email.split("@");
|
|
513
|
+
return domain;
|
|
514
|
+
}
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/client/capxul-deps.ts
|
|
517
|
+
/** The single Context tag the atoms + public methods both resolve. */
|
|
518
|
+
var CapxulDepsTag = class extends Context.Tag("@capxul/sdk/CapxulDeps")() {};
|
|
519
|
+
/** Wrap a bundle as the `Layer<CapxulDepsTag>` the React provider consumes. */
|
|
520
|
+
function capxulDepsLayer(deps) {
|
|
521
|
+
return Layer.succeed(CapxulDepsTag, deps);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Build the 3-port AuthFlow layer FROM the bundle. The single home for this
|
|
525
|
+
* construction — `auth.ts` imports it for its per-method Effect runners, and
|
|
526
|
+
* each program (`getSessionProgram`, …) provides it internally so the actor
|
|
527
|
+
* send's `AuthClientPortTag | ClockPortTag | TelemetryPortTag` requirement is
|
|
528
|
+
* discharged and the program's external requirement stays `CapxulDepsTag`.
|
|
529
|
+
*/
|
|
530
|
+
function authFlowLayer(deps) {
|
|
531
|
+
return Layer.mergeAll(Layer.succeed(AuthClientPortTag, deps.authClient), Layer.succeed(ClockPortTag, deps.clock), Layer.succeed(TelemetryPortTag, deps.telemetry ?? noopTelemetryPort()));
|
|
532
|
+
}
|
|
533
|
+
function noopTelemetryPort() {
|
|
534
|
+
return {
|
|
535
|
+
emit: () => Effect.void,
|
|
536
|
+
identify: () => Effect.void,
|
|
537
|
+
group: () => Effect.void,
|
|
538
|
+
reset: () => Effect.void
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function hasMachineActor(actor) {
|
|
542
|
+
return typeof actor.send === "function";
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* getSession as a single Effect requiring ONLY `CapxulDepsTag`. Faithful
|
|
546
|
+
* transcription of the current public method body (auth.ts:250-281): the
|
|
547
|
+
* leading `signal.aborted` pre-check stays in the thin method wrapper, while a
|
|
548
|
+
* non-aborted signal is forwarded through the actor request options. The
|
|
549
|
+
* `Effect.provide(authFlowLayer(deps))` on the inner actor send
|
|
550
|
+
* is what narrows R from
|
|
551
|
+
* `CapxulDepsTag | AuthClientPortTag | ClockPortTag | TelemetryPortTag` down to
|
|
552
|
+
* `CapxulDepsTag` — the load-bearing structural property the explicit
|
|
553
|
+
* annotation asserts.
|
|
554
|
+
*/
|
|
555
|
+
const getSessionProgramWithOptions = (options) => Effect.gen(function* () {
|
|
556
|
+
const deps = yield* CapxulDepsTag;
|
|
557
|
+
const cached = yield* deps.authCache.getSession.pipe(Effect.mapError((e) => Errors.providerError("auth-cache", "getSession", e)));
|
|
558
|
+
if (cached !== null) return cached;
|
|
559
|
+
if (!hasMachineActor(deps.actor)) {
|
|
560
|
+
const existingError = deps.actor.getSnapshot().context.error;
|
|
561
|
+
if (existingError !== null) return yield* Effect.fail(existingError);
|
|
562
|
+
}
|
|
563
|
+
const refreshed = yield* sendAuthFlowRequest(deps.actor, {
|
|
564
|
+
_tag: "GetSession",
|
|
565
|
+
...depsRequestOptions(deps, options?.signal)
|
|
566
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
|
|
567
|
+
if (refreshed[1].session !== null) return refreshed[1].session;
|
|
568
|
+
const session = deps.actor.getSnapshot().context.session;
|
|
569
|
+
if (session !== null) return session;
|
|
570
|
+
return null;
|
|
571
|
+
});
|
|
572
|
+
getSessionProgramWithOptions();
|
|
573
|
+
/**
|
|
574
|
+
* verifyOtp as a single Effect requiring ONLY `CapxulDepsTag` (PRD #209, slice
|
|
575
|
+
* 3 — parallels {@link getSessionProgram}). Faithful transcription of the
|
|
576
|
+
* current public method body (auth.ts:145-201): the leading `signal.aborted`
|
|
577
|
+
* pre-check stays in the thin method wrapper, while non-aborted per-request
|
|
578
|
+
* signals and the dep-level `invokeTimeoutMs` knob are forwarded. The
|
|
579
|
+
* `Effect.provide(authFlowLayer(
|
|
580
|
+
* deps))` on the inner actor send narrows R from
|
|
581
|
+
* `CapxulDepsTag | AuthClientPortTag | ClockPortTag | TelemetryPortTag` down to
|
|
582
|
+
* `CapxulDepsTag`.
|
|
583
|
+
*/
|
|
584
|
+
function verifyOtpProgram(input, options) {
|
|
585
|
+
return Effect.gen(function* () {
|
|
586
|
+
const deps = yield* CapxulDepsTag;
|
|
587
|
+
if (!/^\d{6}$/.test(input.code)) return yield* Effect.fail(Errors.invalidInput("otp", "must be 6 digits"));
|
|
588
|
+
let email;
|
|
589
|
+
try {
|
|
590
|
+
email = toEmail(input.email);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
return yield* Effect.fail(err instanceof CapxulError ? err : Errors.invalidInput("email", "invalid email"));
|
|
593
|
+
}
|
|
594
|
+
let value = String(deps.actor.getSnapshot().value);
|
|
595
|
+
if (value === "error") {
|
|
596
|
+
yield* sendAuthFlowRequest(deps.actor, { _tag: "ResumeOtpEntry" }).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
|
|
597
|
+
value = "otp_requested";
|
|
598
|
+
} else if (value !== "otp_requested") return yield* Effect.fail(Errors.wrongState({
|
|
599
|
+
method: "verifyOtp",
|
|
600
|
+
currentState: value,
|
|
601
|
+
validStates: ["otp_requested"]
|
|
602
|
+
}));
|
|
603
|
+
if (!hasMachineActor(deps.actor)) {
|
|
604
|
+
const next = deps.actor.getSnapshot();
|
|
605
|
+
if (String(next.value) === "error") return yield* Effect.fail(next.context.error ?? Errors.unknown({ method: "verifyOtp" }));
|
|
606
|
+
}
|
|
607
|
+
const session = yield* sendAuthFlowRequest(deps.actor, {
|
|
608
|
+
_tag: "VerifyOtp",
|
|
609
|
+
email,
|
|
610
|
+
otp: input.code,
|
|
611
|
+
...depsRequestOptions(deps, options?.signal)
|
|
612
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.map((snapshot) => snapshot[1].session), Effect.mapError((failure) => failure.publicError));
|
|
613
|
+
if (session === null) return yield* Effect.fail(Errors.notAuthenticated());
|
|
614
|
+
yield* deps.authCache.setSession(session).pipe(Effect.mapError((e) => Errors.providerError("auth-cache", "setSession", e)));
|
|
615
|
+
return session;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Deps-only request-option builder. The verbatim deps half of `auth.ts`'s
|
|
620
|
+
* former `authRequestOptions(deps, signal)` — the bundle-level
|
|
621
|
+
* `invokeTimeoutMs` and per-request `signal` are forwarded into the actor
|
|
622
|
+
* request options when present.
|
|
623
|
+
*/
|
|
624
|
+
function depsRequestOptions(deps, signal) {
|
|
625
|
+
if (deps.invokeTimeoutMs === void 0 && signal === void 0) return {};
|
|
626
|
+
return { options: {
|
|
627
|
+
...deps.invokeTimeoutMs === void 0 ? {} : { timeoutMs: deps.invokeTimeoutMs },
|
|
628
|
+
...signal === void 0 ? {} : { signal }
|
|
629
|
+
} };
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Clear the local auth cache (session then JWT), mapping each failure into a
|
|
633
|
+
* typed `CapxulError`. Effect transcription of `auth.ts`'s former
|
|
634
|
+
* `clearAuthCache` helper: `clearSession` runs first and short-circuits the
|
|
635
|
+
* JWT clear on failure, matching the original's sequential `if (!ok) return`.
|
|
636
|
+
*/
|
|
637
|
+
function clearAuthCacheEffect(authCache) {
|
|
638
|
+
return authCache.clearSession.pipe(Effect.mapError((cause) => Errors.providerError("auth-cache", "clearSession", cause)), Effect.zipRight(authCache.clearJwt.pipe(Effect.mapError((cause) => Errors.providerError("auth-cache", "clearJwt", cause)))));
|
|
639
|
+
}
|
|
640
|
+
function cryptoRandomId() {
|
|
641
|
+
const bytes = new Uint8Array(8);
|
|
642
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") globalThis.crypto.getRandomValues(bytes);
|
|
643
|
+
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
644
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
645
|
+
}
|
|
646
|
+
const canSendOtpProgram = (input, options) => Effect.gen(function* () {
|
|
647
|
+
const deps = yield* CapxulDepsTag;
|
|
648
|
+
const email = yield* Effect.try({
|
|
649
|
+
try: () => toEmail(input.email),
|
|
650
|
+
catch: (err) => err instanceof CapxulError ? err : Errors.invalidInput("email", "invalid email")
|
|
651
|
+
});
|
|
652
|
+
return yield* deps.authClient.canSendOtp({ email }, options?.signal === void 0 ? void 0 : { signal: options.signal }).pipe(Effect.mapError((failure) => failure.cause instanceof CapxulError ? failure.cause : Errors.providerError("auth-client", "canSendOtp", failure.cause)));
|
|
653
|
+
});
|
|
654
|
+
/**
|
|
655
|
+
* `signIn` as a single Effect requiring ONLY `CapxulDepsTag`. Faithful
|
|
656
|
+
* transcription of the former public method body: brand the email, enforce the
|
|
657
|
+
* idle/otp_requested/error precondition, auto-RESET out of error, drive
|
|
658
|
+
* `RequestOtp`, then mint the tracking Result from the clock. Each actor send
|
|
659
|
+
* provides `authFlowLayer(deps)` INTERNALLY so external R stays `CapxulDepsTag`.
|
|
660
|
+
*/
|
|
661
|
+
const signInProgram = (input, options) => Effect.gen(function* () {
|
|
662
|
+
const deps = yield* CapxulDepsTag;
|
|
663
|
+
const email = yield* Effect.try({
|
|
664
|
+
try: () => toEmail(input.email),
|
|
665
|
+
catch: (err) => err instanceof CapxulError ? err : Errors.invalidInput("email", "invalid email")
|
|
666
|
+
});
|
|
667
|
+
const value = String(deps.actor.getSnapshot().value);
|
|
668
|
+
if (value !== "idle" && value !== "otp_requested" && value !== "error") return yield* Effect.fail(Errors.wrongState({
|
|
669
|
+
method: "signIn",
|
|
670
|
+
currentState: value,
|
|
671
|
+
validStates: [
|
|
672
|
+
"idle",
|
|
673
|
+
"otp_requested",
|
|
674
|
+
"error"
|
|
675
|
+
]
|
|
676
|
+
}));
|
|
677
|
+
if (value === "error") yield* sendAuthFlowRequest(deps.actor, {
|
|
678
|
+
_tag: "ResetAuthFlow",
|
|
679
|
+
...depsRequestOptions(deps, options?.signal)
|
|
680
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
|
|
681
|
+
yield* sendAuthFlowRequest(deps.actor, {
|
|
682
|
+
_tag: "RequestOtp",
|
|
683
|
+
email,
|
|
684
|
+
...depsRequestOptions(deps, options?.signal)
|
|
685
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
|
|
686
|
+
const now = yield* deps.clock.now.pipe(Effect.mapError((cause) => Errors.providerError("clock", "now", cause)));
|
|
687
|
+
return {
|
|
688
|
+
sessionId: cryptoRandomId(),
|
|
689
|
+
expiresAt: now + (deps.otpTtlMs ?? 3e5)
|
|
690
|
+
};
|
|
691
|
+
});
|
|
692
|
+
/**
|
|
693
|
+
* `signOut` as a single Effect requiring ONLY `CapxulDepsTag`. Faithful
|
|
694
|
+
* transcription of the former public method body: WRONG_STATE from idle,
|
|
695
|
+
* reset-and-clear from error, otherwise drive `SignOut` and clear the local
|
|
696
|
+
* cache REGARDLESS of the wire outcome. `Effect.either` captures the `SignOut`
|
|
697
|
+
* result without short-circuiting so the cache clear always runs — a cache
|
|
698
|
+
* clear failure then takes precedence over the wire error, matching the former
|
|
699
|
+
* `if (!cacheClear.ok) return cacheClear; return result`.
|
|
700
|
+
*/
|
|
701
|
+
const signOutProgramWithOptions = (options) => Effect.gen(function* () {
|
|
702
|
+
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
|
+
}));
|
|
715
|
+
if (value === "error") {
|
|
716
|
+
yield* sendAuthFlowRequest(deps.actor, {
|
|
717
|
+
_tag: "ResetAuthFlow",
|
|
718
|
+
...depsRequestOptions(deps, options?.signal)
|
|
719
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError));
|
|
720
|
+
yield* clearAuthCacheEffect(deps.authCache);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const signOutResult = yield* sendAuthFlowRequest(deps.actor, {
|
|
724
|
+
_tag: "SignOut",
|
|
725
|
+
...depsRequestOptions(deps, options?.signal)
|
|
726
|
+
}).pipe(Effect.provide(authFlowLayer(deps)), Effect.mapError((failure) => failure.publicError), Effect.either);
|
|
727
|
+
yield* clearAuthCacheEffect(deps.authCache);
|
|
728
|
+
if (Either.isLeft(signOutResult)) return yield* Effect.fail(signOutResult.left);
|
|
729
|
+
});
|
|
730
|
+
signOutProgramWithOptions();
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/client/to-capxul-result.ts
|
|
733
|
+
async function toCapxulResult(program, layer) {
|
|
734
|
+
const result = await Effect.runPromise(program.pipe(Effect.provide(layer), Effect.catchAllDefect((defect) => Effect.fail(Errors.unknown(defect))), Effect.either));
|
|
735
|
+
if (Either.isLeft(result)) return {
|
|
736
|
+
ok: false,
|
|
737
|
+
error: result.left
|
|
738
|
+
};
|
|
739
|
+
return {
|
|
740
|
+
ok: true,
|
|
741
|
+
value: result.right
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/client/auth.ts
|
|
746
|
+
function makeAuthMethods(deps) {
|
|
747
|
+
return {
|
|
748
|
+
async canSendOtp(input, options) {
|
|
749
|
+
if (options?.signal?.aborted) return {
|
|
750
|
+
ok: false,
|
|
751
|
+
error: Errors.cancelled({ operation: "canSendOtp" })
|
|
752
|
+
};
|
|
753
|
+
return toCapxulResult(canSendOtpProgram(input, options), capxulDepsLayer(deps));
|
|
754
|
+
},
|
|
755
|
+
async signIn(input, options) {
|
|
756
|
+
if (options?.signal?.aborted) return {
|
|
757
|
+
ok: false,
|
|
758
|
+
error: Errors.cancelled({ operation: "signIn" })
|
|
759
|
+
};
|
|
760
|
+
return toCapxulResult(signInProgram(input, options), capxulDepsLayer(deps));
|
|
761
|
+
},
|
|
762
|
+
async verifyOtp(input, options) {
|
|
763
|
+
if (options?.signal?.aborted) return {
|
|
764
|
+
ok: false,
|
|
765
|
+
error: Errors.cancelled({ operation: "verifyOtp" })
|
|
766
|
+
};
|
|
767
|
+
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);
|
|
771
|
+
}
|
|
772
|
+
return result;
|
|
773
|
+
},
|
|
774
|
+
async signOut(options) {
|
|
775
|
+
if (options?.signal?.aborted) return {
|
|
776
|
+
ok: false,
|
|
777
|
+
error: Errors.cancelled({ operation: "signOut" })
|
|
778
|
+
};
|
|
779
|
+
return toCapxulResult(signOutProgramWithOptions(options), capxulDepsLayer(deps));
|
|
780
|
+
},
|
|
781
|
+
async getSession(options) {
|
|
782
|
+
if (options?.signal?.aborted) return {
|
|
783
|
+
ok: false,
|
|
784
|
+
error: Errors.cancelled({ operation: "getSession" })
|
|
785
|
+
};
|
|
786
|
+
return toCapxulResult(getSessionProgramWithOptions(options), capxulDepsLayer(deps));
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region src/client/_shared/provisioning-telemetry.ts
|
|
792
|
+
async function emitProvisioningTelemetry(telemetry, smartAccount) {
|
|
793
|
+
if (telemetry === void 0) return;
|
|
794
|
+
await Effect.runPromise(telemetry.emit({
|
|
795
|
+
name: "provisioning_safe_created",
|
|
796
|
+
props: { safe_address: smartAccount.smartAccountAddress }
|
|
797
|
+
}).pipe(Effect.catchAll((cause) => Effect.sync(() => reportProvisioningTelemetryFailure("error", smartAccount, cause))), Effect.catchAllDefect((cause) => Effect.sync(() => reportProvisioningTelemetryFailure("defect", smartAccount, cause)))));
|
|
798
|
+
}
|
|
799
|
+
function reportProvisioningTelemetryFailure(kind, smartAccount, cause) {
|
|
800
|
+
if (!isProvisioningTelemetryDebugEnabled()) return;
|
|
801
|
+
globalThis.console?.warn?.("[capxul] provisioning telemetry dropped", {
|
|
802
|
+
kind,
|
|
803
|
+
safeAddress: smartAccount.smartAccountAddress,
|
|
804
|
+
cause
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function isProvisioningTelemetryDebugEnabled() {
|
|
808
|
+
return globalThis.process?.env?.CAPXUL_DEBUG_TELEMETRY === "1";
|
|
809
|
+
}
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/client/smart-account-deps.ts
|
|
812
|
+
/** The single Context tag the `smartAccount.*` methods resolve. */
|
|
813
|
+
var SmartAccountDepsTag = class extends Context.Tag("@capxul/sdk/SmartAccountDeps")() {};
|
|
814
|
+
/** Wrap a bundle as the `Layer<SmartAccountDepsTag>` the React provider consumes. */
|
|
815
|
+
function smartAccountDepsLayer(deps) {
|
|
816
|
+
return Layer.succeed(SmartAccountDepsTag, deps);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* `smartAccount.loadCurrent` as a single Effect requiring ONLY
|
|
820
|
+
* `SmartAccountDepsTag`. Faithful transcription of the current public method
|
|
821
|
+
* body (`smart-account.ts`): snapshot the actor for the session authUserId,
|
|
822
|
+
* append the optional sentinel fallback, and return the first non-null
|
|
823
|
+
* `SmartAccountPort` row. The leading `signal.aborted` pre-check stays in the
|
|
824
|
+
* thin method wrapper. Port failures narrow to their `publicError`.
|
|
825
|
+
*/
|
|
826
|
+
const loadSmartAccountProgram = Effect.gen(function* () {
|
|
827
|
+
const deps = yield* SmartAccountDepsTag;
|
|
828
|
+
const snap = deps.actor.getSnapshot();
|
|
829
|
+
const fallback = deps.sessionAuthUserIdFallback ?? null;
|
|
830
|
+
const candidates = [];
|
|
831
|
+
if (snap.context.session?.authUserId !== void 0) candidates.push(snap.context.session.authUserId);
|
|
832
|
+
if (fallback !== null) candidates.push(fallback);
|
|
833
|
+
for (const candidate of candidates) {
|
|
834
|
+
const row = yield* deps.smartAccountPort.loadByAuthUserId(candidate).pipe(Effect.mapError((failure) => failure.publicError));
|
|
835
|
+
if (row !== null) return row;
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
});
|
|
839
|
+
/**
|
|
840
|
+
* `smartAccount.provision` as an input-parameterised program requiring ONLY
|
|
841
|
+
* `SmartAccountDepsTag`. Faithful transcription of the current public method
|
|
842
|
+
* body (`smart-account.ts`): precondition the actor on `authenticated`, brand
|
|
843
|
+
* the external signer at entry, then drive the `SmartAccountPort.provision`
|
|
844
|
+
* port. The leading `signal.aborted` pre-check stays in the thin method
|
|
845
|
+
* wrapper (provision currently takes no signal, so there is none).
|
|
846
|
+
*/
|
|
847
|
+
function provisionSmartAccountProgram(input) {
|
|
848
|
+
return Effect.gen(function* () {
|
|
849
|
+
const deps = yield* SmartAccountDepsTag;
|
|
850
|
+
const snap = deps.actor.getSnapshot();
|
|
851
|
+
const value = snap.value;
|
|
852
|
+
if (value !== "authenticated") return yield* Effect.fail(Errors.wrongState({
|
|
853
|
+
method: "smartAccount.provision",
|
|
854
|
+
currentState: value,
|
|
855
|
+
validStates: ["authenticated"]
|
|
856
|
+
}));
|
|
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
|
+
const session = snap.context.session;
|
|
866
|
+
if (session === null) return yield* Effect.fail(Errors.notAuthenticated());
|
|
867
|
+
const provisioned = yield* deps.smartAccountPort.provision({
|
|
868
|
+
authUserId: session.authUserId,
|
|
869
|
+
signerAddress,
|
|
870
|
+
chainId: deps.chainId
|
|
871
|
+
}).pipe(Effect.mapError((failure) => failure.publicError));
|
|
872
|
+
yield* Effect.promise(() => emitProvisioningTelemetry(deps.telemetry, provisioned));
|
|
873
|
+
return provisioned;
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/client/smart-account.ts
|
|
878
|
+
function makeSmartAccountMethods(deps) {
|
|
879
|
+
return {
|
|
880
|
+
async loadCurrent(options) {
|
|
881
|
+
if (options?.signal?.aborted) return {
|
|
882
|
+
ok: false,
|
|
883
|
+
error: Errors.cancelled({ operation: "smartAccount.loadCurrent" })
|
|
884
|
+
};
|
|
885
|
+
return toCapxulResult(loadSmartAccountProgram, smartAccountDepsLayer(deps));
|
|
886
|
+
},
|
|
887
|
+
async provision(input) {
|
|
888
|
+
return toCapxulResult(provisionSmartAccountProgram(input), smartAccountDepsLayer(deps));
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
//#endregion
|
|
893
|
+
//#region src/ports/identity.ts
|
|
894
|
+
var IdentityError = class extends Data.TaggedError("IdentityError") {};
|
|
895
|
+
function identityErrorFromCapxul(operation, error, cause = error) {
|
|
896
|
+
return new IdentityError({
|
|
897
|
+
operation,
|
|
898
|
+
publicCode: error.code,
|
|
899
|
+
publicError: error,
|
|
900
|
+
cause,
|
|
901
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
var IdentityPortTag = class extends Context.Tag("@capxul/sdk/ports/IdentityPort")() {};
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/client/identity-deps.ts
|
|
907
|
+
/** The single Context tag the profile atom + `identity.loadCurrent` resolve. */
|
|
908
|
+
var IdentityDepsTag = class extends Context.Tag("@capxul/sdk/IdentityDeps")() {};
|
|
909
|
+
/** Wrap a bundle as the `Layer<IdentityDepsTag>` the React provider consumes. */
|
|
910
|
+
function identityDepsLayer(deps) {
|
|
911
|
+
return Layer.succeed(IdentityDepsTag, deps);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* `identity.loadCurrent` as a single Effect requiring ONLY `IdentityDepsTag`.
|
|
915
|
+
* Faithful transcription of the current public method body (`identity.ts`):
|
|
916
|
+
* the leading `signal.aborted` pre-check stays in the thin method wrapper;
|
|
917
|
+
* the program snapshots the actor session, then narrows the `IdentityPort`
|
|
918
|
+
* failure to its `publicError`. Defects/sync-throws surface as `UNKNOWN`
|
|
919
|
+
* through `toCapxulResult`'s `catchAllDefect` (and the atom's runtime), which
|
|
920
|
+
* replaces the old outer `try/catch`.
|
|
921
|
+
*/
|
|
922
|
+
const loadIdentityProgram = Effect.gen(function* () {
|
|
923
|
+
const deps = yield* IdentityDepsTag;
|
|
924
|
+
if (deps.actor === void 0) return yield* Effect.fail(Errors.notAuthenticated());
|
|
925
|
+
const snapshot = deps.actor.getSnapshot();
|
|
926
|
+
const existingError = snapshot.context.error;
|
|
927
|
+
if (existingError instanceof CapxulError) return yield* Effect.fail(existingError);
|
|
928
|
+
const authUserId = snapshot.context.session?.authUserId;
|
|
929
|
+
if (authUserId === void 0) return null;
|
|
930
|
+
return yield* deps.identityPort.loadByAuthUserId(authUserId).pipe(Effect.catchAllDefect((cause) => Effect.fail(identityErrorFromCapxul("loadByAuthUserId", cause instanceof CapxulError ? cause : Errors.unknown(cause), cause))), Effect.mapError((failure) => failure.publicError));
|
|
931
|
+
});
|
|
932
|
+
//#endregion
|
|
933
|
+
//#region src/client/identity.ts
|
|
934
|
+
function makeIdentityMethods(deps) {
|
|
935
|
+
return { async loadCurrent(options) {
|
|
936
|
+
if (options?.signal?.aborted) return {
|
|
937
|
+
ok: false,
|
|
938
|
+
error: Errors.cancelled({ operation: "identity.loadCurrent" })
|
|
939
|
+
};
|
|
940
|
+
return toCapxulResult(loadIdentityProgram, identityDepsLayer(deps));
|
|
941
|
+
} };
|
|
942
|
+
}
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/client/_shared/effect-actor-bridge.ts
|
|
945
|
+
/**
|
|
946
|
+
* Bridge an Effect whose typed failure carries `{ publicError: CapxulError }`
|
|
947
|
+
* into the `Promise<CapxulResult<T>>` shape the consumer-facing method
|
|
948
|
+
* bundles return. Applies to every port that surfaces a `publicError`
|
|
949
|
+
* (smart-account, identity, auth-cache, and — post-#161 — account
|
|
950
|
+
* provision/deploy).
|
|
951
|
+
*/
|
|
952
|
+
async function runPortEffect(effect) {
|
|
953
|
+
const result = await Effect.runPromise(Effect.either(effect));
|
|
954
|
+
if (Either.isRight(result)) return {
|
|
955
|
+
ok: true,
|
|
956
|
+
value: result.right
|
|
957
|
+
};
|
|
958
|
+
return {
|
|
959
|
+
ok: false,
|
|
960
|
+
error: result.left.publicError
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/** The session carried by the actor's current snapshot, or `null`. */
|
|
964
|
+
function sessionFromActor(actor) {
|
|
965
|
+
return actor.getSnapshot().context.session;
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region src/client/account-deps.ts
|
|
969
|
+
/** The single Context tag the account atom + `account.getStatus` resolve. */
|
|
970
|
+
var AccountDepsTag = class extends Context.Tag("@capxul/sdk/AccountDeps")() {};
|
|
971
|
+
/** Wrap a bundle as the `Layer<AccountDepsTag>` the React provider consumes. */
|
|
972
|
+
function accountDepsLayer(deps) {
|
|
973
|
+
return Layer.succeed(AccountDepsTag, deps);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* `account.getStatus` as a single Effect requiring ONLY `AccountDepsTag`.
|
|
977
|
+
* Faithful transcription of the current public method body (`account.ts`):
|
|
978
|
+
* resolve the session (actor first, then the resume-path `authCache`), then
|
|
979
|
+
* walk the readiness ladder. The `SmartAccountPort` failure narrows to its
|
|
980
|
+
* `publicError`; a consumer `AccountProvider.getAddress` rejection becomes a
|
|
981
|
+
* `providerError` (defending the public `CapxulResult` contract).
|
|
982
|
+
*/
|
|
983
|
+
const accountStatusProgram = Effect.gen(function* () {
|
|
984
|
+
const deps = yield* AccountDepsTag;
|
|
985
|
+
const session = yield* Effect.promise(() => currentSession(deps.actor, deps.authCache));
|
|
986
|
+
if (session === null) return { status: "notAuthenticated" };
|
|
987
|
+
const current = yield* deps.smartAccountPort.loadByAuthUserId(session.authUserId).pipe(Effect.mapError((failure) => failure.publicError));
|
|
988
|
+
if (current !== null) return statusFromAccount(current, deps.requirement);
|
|
989
|
+
if (deps.requirement === "none") return {
|
|
990
|
+
status: "accountReady",
|
|
991
|
+
requirement: deps.requirement,
|
|
992
|
+
account: null,
|
|
993
|
+
deployment: { status: "counterfactual" }
|
|
994
|
+
};
|
|
995
|
+
const signer = deps.signer;
|
|
996
|
+
if (signer === void 0) return {
|
|
997
|
+
status: "accountRequired",
|
|
998
|
+
requirement: deps.requirement,
|
|
999
|
+
chainId: deps.chainId
|
|
1000
|
+
};
|
|
1001
|
+
const signerAddress = yield* Effect.tryPromise({
|
|
1002
|
+
try: () => signer.getAddress(),
|
|
1003
|
+
catch: (cause) => Errors.providerError("signer", "getAddress", cause)
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
status: "accountProviderReady",
|
|
1007
|
+
requirement: deps.requirement,
|
|
1008
|
+
chainId: deps.chainId,
|
|
1009
|
+
source: signer.source,
|
|
1010
|
+
signerAddress
|
|
1011
|
+
};
|
|
1012
|
+
});
|
|
1013
|
+
/**
|
|
1014
|
+
* Resume-path session resolution. Consults the actor first; if the actor has
|
|
1015
|
+
* no session (e.g. page refresh before any sign-in event), reads
|
|
1016
|
+
* `authCache.getSession`. `AuthCacheError` is non-fatal at this read point —
|
|
1017
|
+
* treat as "no session" and let the consumer's `auth.signIn` path resolve.
|
|
1018
|
+
*/
|
|
1019
|
+
async function currentSession(actor, authCache) {
|
|
1020
|
+
const fromActor = sessionFromActor(actor);
|
|
1021
|
+
if (fromActor !== null) return fromActor;
|
|
1022
|
+
if (authCache === void 0) return null;
|
|
1023
|
+
const cached = await Effect.runPromise(Effect.either(authCache.getSession));
|
|
1024
|
+
if (Either.isRight(cached)) return cached.right;
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
/** Map a backend `SmartAccount` row onto the readiness `AccountStatus`. */
|
|
1028
|
+
function statusFromAccount(account, requirement) {
|
|
1029
|
+
if (requirement === "deployed") {
|
|
1030
|
+
if (account.deployedAt === null) return {
|
|
1031
|
+
status: "accountPrepared",
|
|
1032
|
+
requirement,
|
|
1033
|
+
account,
|
|
1034
|
+
deployment: { status: "counterfactual" }
|
|
1035
|
+
};
|
|
1036
|
+
return {
|
|
1037
|
+
status: "accountReady",
|
|
1038
|
+
requirement,
|
|
1039
|
+
account,
|
|
1040
|
+
deployment: {
|
|
1041
|
+
status: "deployed",
|
|
1042
|
+
deployedAt: account.deployedAt
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
status: "accountReady",
|
|
1048
|
+
requirement,
|
|
1049
|
+
account,
|
|
1050
|
+
deployment: account.deployedAt === null ? { status: "counterfactual" } : {
|
|
1051
|
+
status: "deployed",
|
|
1052
|
+
deployedAt: account.deployedAt
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/client/account.ts
|
|
1058
|
+
function makeAccountMethods(deps) {
|
|
1059
|
+
toChainId(deps.chainId);
|
|
1060
|
+
const requirement = deps.requirement;
|
|
1061
|
+
const getStatus = () => toCapxulResult(accountStatusProgram, accountDepsLayer(deps));
|
|
1062
|
+
const ensureIdentityProfile = async (session) => {
|
|
1063
|
+
const existing = await runPortEffect(deps.identityPort.loadByAuthUserId(session.authUserId));
|
|
1064
|
+
if (!existing.ok) return {
|
|
1065
|
+
ok: false,
|
|
1066
|
+
error: existing.error
|
|
1067
|
+
};
|
|
1068
|
+
if (existing.value !== null) return {
|
|
1069
|
+
ok: true,
|
|
1070
|
+
value: void 0
|
|
1071
|
+
};
|
|
1072
|
+
const created = await runPortEffect(deps.identityPort.create({
|
|
1073
|
+
authUserId: session.authUserId,
|
|
1074
|
+
email: session.email
|
|
1075
|
+
}));
|
|
1076
|
+
if (!created.ok) return {
|
|
1077
|
+
ok: false,
|
|
1078
|
+
error: created.error
|
|
1079
|
+
};
|
|
1080
|
+
return {
|
|
1081
|
+
ok: true,
|
|
1082
|
+
value: void 0
|
|
1083
|
+
};
|
|
1084
|
+
};
|
|
1085
|
+
const provisionImpl = async () => {
|
|
1086
|
+
const session = await currentSession(deps.actor, deps.authCache);
|
|
1087
|
+
if (session === null) return {
|
|
1088
|
+
ok: false,
|
|
1089
|
+
error: Errors.notAuthenticated()
|
|
1090
|
+
};
|
|
1091
|
+
const profileReady = await ensureIdentityProfile(session);
|
|
1092
|
+
if (!profileReady.ok) return {
|
|
1093
|
+
ok: false,
|
|
1094
|
+
error: profileReady.error
|
|
1095
|
+
};
|
|
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
|
+
const provisioned = await runPortEffect(deps.smartAccountPort.provision({
|
|
1108
|
+
authUserId: session.authUserId,
|
|
1109
|
+
signerAddress,
|
|
1110
|
+
chainId: toChainId(deps.chainId)
|
|
1111
|
+
}));
|
|
1112
|
+
if (!provisioned.ok) return provisioned;
|
|
1113
|
+
await emitProvisioningTelemetry(deps.telemetry, provisioned.value);
|
|
1114
|
+
return {
|
|
1115
|
+
ok: true,
|
|
1116
|
+
value: provisioned.value
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1119
|
+
const deployMutex = /* @__PURE__ */ new Map();
|
|
1120
|
+
const deploySafeImpl = async () => {
|
|
1121
|
+
const session = await currentSession(deps.actor, deps.authCache);
|
|
1122
|
+
if (session === null) return {
|
|
1123
|
+
ok: false,
|
|
1124
|
+
error: Errors.notAuthenticated()
|
|
1125
|
+
};
|
|
1126
|
+
const signer = deps.signer;
|
|
1127
|
+
if (signer === void 0) return {
|
|
1128
|
+
ok: false,
|
|
1129
|
+
error: Errors.smartAccountMissing("deploySafe")
|
|
1130
|
+
};
|
|
1131
|
+
const existing = await runPortEffect(deps.smartAccountPort.loadByAuthUserId(session.authUserId));
|
|
1132
|
+
if (!existing.ok) return existing;
|
|
1133
|
+
if (existing.value !== null && existing.value.deployedAt !== null) return {
|
|
1134
|
+
ok: true,
|
|
1135
|
+
value: existing.value
|
|
1136
|
+
};
|
|
1137
|
+
if (existing.value === null) {
|
|
1138
|
+
const provisioned = await provisionImpl();
|
|
1139
|
+
if (!provisioned.ok) return provisioned;
|
|
1140
|
+
}
|
|
1141
|
+
const mutexKey = `${String(session.authUserId)}:${deps.chainId}`;
|
|
1142
|
+
const inFlight = deployMutex.get(mutexKey);
|
|
1143
|
+
if (inFlight !== void 0) return inFlight;
|
|
1144
|
+
const work = runBackendDeploy({
|
|
1145
|
+
authUserId: session.authUserId,
|
|
1146
|
+
chainId: deps.chainId,
|
|
1147
|
+
signer,
|
|
1148
|
+
smartAccountPort: deps.smartAccountPort
|
|
1149
|
+
}).finally(() => {
|
|
1150
|
+
deployMutex.delete(mutexKey);
|
|
1151
|
+
});
|
|
1152
|
+
deployMutex.set(mutexKey, work);
|
|
1153
|
+
return work;
|
|
1154
|
+
};
|
|
1155
|
+
const ensureReady = async () => {
|
|
1156
|
+
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 {
|
|
1174
|
+
ok: false,
|
|
1175
|
+
error: deployed.error
|
|
1176
|
+
};
|
|
1177
|
+
return getStatus();
|
|
1178
|
+
}
|
|
1179
|
+
if (status.value.status === "accountPrepared" && requirement === "deployed") {
|
|
1180
|
+
const deployed = await deploySafeImpl();
|
|
1181
|
+
if (!deployed.ok) return {
|
|
1182
|
+
ok: false,
|
|
1183
|
+
error: deployed.error
|
|
1184
|
+
};
|
|
1185
|
+
return getStatus();
|
|
1186
|
+
}
|
|
1187
|
+
return status;
|
|
1188
|
+
};
|
|
1189
|
+
return {
|
|
1190
|
+
getStatus,
|
|
1191
|
+
ensureReady,
|
|
1192
|
+
_internal: {
|
|
1193
|
+
provision: provisionImpl,
|
|
1194
|
+
deploySafe: deploySafeImpl
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
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
|
+
/**
|
|
1212
|
+
* Backend-orchestrated deploy (backend-orchestrated-deploy.md):
|
|
1213
|
+
* 1. `deployPrepare` — backend builds + gas-fills + paymaster-sponsors the
|
|
1214
|
+
* deployment UserOp and returns the EIP-712 SafeOp digest.
|
|
1215
|
+
* 2. `signer.signUserOpHash(digest)` — the only step needing the user's key.
|
|
1216
|
+
* 3. `deploySubmit` — backend submits via the bundler + confirms deployedAt.
|
|
1217
|
+
*/
|
|
1218
|
+
async function runBackendDeploy(input) {
|
|
1219
|
+
const chainId = toChainId(input.chainId);
|
|
1220
|
+
const prepared = await runPortEffect(input.smartAccountPort.deployPrepare({
|
|
1221
|
+
authUserId: input.authUserId,
|
|
1222
|
+
chainId
|
|
1223
|
+
}));
|
|
1224
|
+
if (!prepared.ok) return prepared;
|
|
1225
|
+
let signature;
|
|
1226
|
+
try {
|
|
1227
|
+
signature = await input.signer.signUserOpHash(prepared.value.digest);
|
|
1228
|
+
} catch (cause) {
|
|
1229
|
+
return {
|
|
1230
|
+
ok: false,
|
|
1231
|
+
error: Errors.providerError("signer", "signUserOpHash", cause)
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
const confirmed = await runPortEffect(input.smartAccountPort.deploySubmit({
|
|
1235
|
+
authUserId: input.authUserId,
|
|
1236
|
+
chainId,
|
|
1237
|
+
userOp: prepared.value.userOp,
|
|
1238
|
+
signature
|
|
1239
|
+
}));
|
|
1240
|
+
if (!confirmed.ok) return confirmed;
|
|
1241
|
+
if (confirmed.value.deployedAt === null) return {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
error: Errors.providerError("smart-account", "deploySubmit", "missing deployedAt")
|
|
1244
|
+
};
|
|
1245
|
+
return {
|
|
1246
|
+
ok: true,
|
|
1247
|
+
value: confirmed.value
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/money/to-wei.ts
|
|
1252
|
+
/**
|
|
1253
|
+
* Lower consumer `Money` into an on-chain integer string for guarded writes.
|
|
1254
|
+
* Canon `account-balance-model.md` §10 — internal at the SDK boundary only.
|
|
1255
|
+
*/
|
|
1256
|
+
function toWei(money) {
|
|
1257
|
+
return parseUnits(money.value, money.decimals).toString();
|
|
1258
|
+
}
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/client/_shared/money-telemetry.ts
|
|
1261
|
+
/**
|
|
1262
|
+
* Lower a consumer `Money` into a non-negative integer WeiAmount string for
|
|
1263
|
+
* telemetry props. Reuses the SDK boundary `toWei` (viem `parseUnits`, string
|
|
1264
|
+
* math only — never a float multiply per `.claude/rules/numeric-parsing.md`).
|
|
1265
|
+
*/
|
|
1266
|
+
function moneyToWeiAmount(money) {
|
|
1267
|
+
return toWei(money);
|
|
1268
|
+
}
|
|
1269
|
+
function transferDirection(from, to) {
|
|
1270
|
+
if (from === "main") return "add";
|
|
1271
|
+
if (to === "main") return "out";
|
|
1272
|
+
return "between";
|
|
1273
|
+
}
|
|
1274
|
+
/** Fire-and-forget emit through an optional port; never throws. */
|
|
1275
|
+
async function emitMoneyTelemetry(telemetry, event) {
|
|
1276
|
+
if (telemetry === void 0) return;
|
|
1277
|
+
await Effect.runPromise(telemetry.emit(event).pipe(Effect.catchAll((cause) => Effect.sync(() => reportMoneyTelemetryFailure("error", event.name, cause))), Effect.catchAllDefect((cause) => Effect.sync(() => reportMoneyTelemetryFailure("defect", event.name, cause)))));
|
|
1278
|
+
}
|
|
1279
|
+
function emitFaucetRequested(telemetry, input) {
|
|
1280
|
+
let amount;
|
|
1281
|
+
try {
|
|
1282
|
+
amount = moneyToWeiAmount(input.amount);
|
|
1283
|
+
} catch (cause) {
|
|
1284
|
+
reportMoneyTelemetryFailure("defect", "faucet_requested", cause);
|
|
1285
|
+
return Promise.resolve();
|
|
1286
|
+
}
|
|
1287
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1288
|
+
name: "faucet_requested",
|
|
1289
|
+
props: {
|
|
1290
|
+
amount,
|
|
1291
|
+
chainId: input.chainId
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
function emitFaucetFailed(telemetry, reason) {
|
|
1296
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1297
|
+
name: "faucet_failed",
|
|
1298
|
+
props: { reason }
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function emitAccountBalanceFailed(telemetry, reason) {
|
|
1302
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1303
|
+
name: "account_balance_failed",
|
|
1304
|
+
props: { reason }
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
function emitTransferRequested(telemetry, input) {
|
|
1308
|
+
let amount;
|
|
1309
|
+
try {
|
|
1310
|
+
amount = moneyToWeiAmount(input.amount);
|
|
1311
|
+
} catch (cause) {
|
|
1312
|
+
reportMoneyTelemetryFailure("defect", "transfer_requested", cause);
|
|
1313
|
+
return Promise.resolve();
|
|
1314
|
+
}
|
|
1315
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1316
|
+
name: "transfer_requested",
|
|
1317
|
+
props: {
|
|
1318
|
+
amount,
|
|
1319
|
+
direction: transferDirection(input.from, input.to)
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
function emitTransferFailed(telemetry, reason) {
|
|
1324
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1325
|
+
name: "transfer_failed",
|
|
1326
|
+
props: { reason }
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
function emitSubAccountCreated(telemetry, subAccountId) {
|
|
1330
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1331
|
+
name: "subaccount_created",
|
|
1332
|
+
props: { sub_account_id: subAccountId }
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
function emitSubAccountRenamed(telemetry, subAccountId) {
|
|
1336
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1337
|
+
name: "subaccount_renamed",
|
|
1338
|
+
props: { sub_account_id: subAccountId }
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
function emitSubAccountDeleted(telemetry, subAccountId) {
|
|
1342
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1343
|
+
name: "subaccount_deleted",
|
|
1344
|
+
props: { sub_account_id: subAccountId }
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
function emitSubAccountOpFailed(telemetry, op, reason) {
|
|
1348
|
+
return emitMoneyTelemetry(telemetry, {
|
|
1349
|
+
name: "subaccount_op_failed",
|
|
1350
|
+
props: {
|
|
1351
|
+
op,
|
|
1352
|
+
reason
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function reportMoneyTelemetryFailure(kind, name, cause) {
|
|
1357
|
+
if (!isMoneyTelemetryDebugEnabled()) return;
|
|
1358
|
+
globalThis.console?.warn?.("[capxul] money telemetry dropped", {
|
|
1359
|
+
kind,
|
|
1360
|
+
name,
|
|
1361
|
+
cause
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
function isMoneyTelemetryDebugEnabled() {
|
|
1365
|
+
return globalThis.process?.env?.CAPXUL_DEBUG_TELEMETRY === "1";
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region src/client/accounts-deps.ts
|
|
1369
|
+
var AccountsDepsTag = class extends Context.Tag("@capxul/sdk/AccountsDeps")() {};
|
|
1370
|
+
function accountsDepsLayer(deps) {
|
|
1371
|
+
return Layer.succeed(AccountsDepsTag, deps);
|
|
1372
|
+
}
|
|
1373
|
+
const readAccountProgram = Effect.gen(function* () {
|
|
1374
|
+
const deps = yield* AccountsDepsTag;
|
|
1375
|
+
return yield* deps.accountReadPort.readBalance({ chainId: deps.chainId }).pipe(Effect.tapError((error) => Effect.promise(() => emitAccountBalanceFailed(deps.telemetry, error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1376
|
+
});
|
|
1377
|
+
function fundFromFaucetProgram(amount) {
|
|
1378
|
+
return Effect.gen(function* () {
|
|
1379
|
+
const deps = yield* AccountsDepsTag;
|
|
1380
|
+
yield* Effect.promise(() => emitFaucetRequested(deps.telemetry, {
|
|
1381
|
+
amount,
|
|
1382
|
+
chainId: deps.chainId
|
|
1383
|
+
}));
|
|
1384
|
+
return yield* deps.accountReadPort.fundFromFaucet({
|
|
1385
|
+
chainId: deps.chainId,
|
|
1386
|
+
amount
|
|
1387
|
+
}).pipe(Effect.tapError((error) => Effect.promise(() => emitFaucetFailed(deps.telemetry, error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
//#endregion
|
|
1391
|
+
//#region src/client/accounts.ts
|
|
1392
|
+
/**
|
|
1393
|
+
* Build the accounts bundle. `assembleCapxulClient` exposes the public `read`
|
|
1394
|
+
* half at `client.accounts` (typed `AccountsMethods`) and the dev-only `fund`
|
|
1395
|
+
* half at `client._internal.accounts` (typed `AccountsFaucetMethods`), so the
|
|
1396
|
+
* faucet never appears on the public client surface.
|
|
1397
|
+
*/
|
|
1398
|
+
function makeAccountsMethods(deps) {
|
|
1399
|
+
return {
|
|
1400
|
+
async read(options) {
|
|
1401
|
+
if (options?.signal?.aborted) return {
|
|
1402
|
+
ok: false,
|
|
1403
|
+
error: Errors.cancelled({ operation: "accounts.read" })
|
|
1404
|
+
};
|
|
1405
|
+
return toCapxulResult(readAccountProgram, accountsDepsLayer(deps));
|
|
1406
|
+
},
|
|
1407
|
+
async fund(amount, options) {
|
|
1408
|
+
if (options?.signal?.aborted) return {
|
|
1409
|
+
ok: false,
|
|
1410
|
+
error: Errors.cancelled({ operation: "accounts.fund" })
|
|
1411
|
+
};
|
|
1412
|
+
return toCapxulResult(fundFromFaucetProgram(amount), accountsDepsLayer(deps));
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
//#endregion
|
|
1417
|
+
//#region src/client/sub-accounts-deps.ts
|
|
1418
|
+
var SubAccountsDepsTag = class extends Context.Tag("@capxul/sdk/SubAccountsDeps")() {};
|
|
1419
|
+
function subAccountsDepsLayer(deps) {
|
|
1420
|
+
return Layer.succeed(SubAccountsDepsTag, deps);
|
|
1421
|
+
}
|
|
1422
|
+
function createSubAccountProgram(accountId, name) {
|
|
1423
|
+
return Effect.gen(function* () {
|
|
1424
|
+
const deps = yield* SubAccountsDepsTag;
|
|
1425
|
+
const created = yield* deps.subAccountPort.create({
|
|
1426
|
+
accountId,
|
|
1427
|
+
name
|
|
1428
|
+
}).pipe(Effect.tapError((error) => Effect.promise(() => emitSubAccountOpFailed(deps.telemetry, "create", error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1429
|
+
yield* Effect.promise(() => emitSubAccountCreated(deps.telemetry, created.id));
|
|
1430
|
+
return created;
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function getSubAccountProgram(subAccountId) {
|
|
1434
|
+
return Effect.gen(function* () {
|
|
1435
|
+
return yield* (yield* SubAccountsDepsTag).subAccountPort.get({ subAccountId }).pipe(Effect.mapError((error) => error.publicError));
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
function listSubAccountsProgram(accountId) {
|
|
1439
|
+
return Effect.gen(function* () {
|
|
1440
|
+
return yield* (yield* SubAccountsDepsTag).subAccountPort.list({ accountId }).pipe(Effect.mapError((error) => error.publicError));
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
function renameSubAccountProgram(subAccountId, name) {
|
|
1444
|
+
return Effect.gen(function* () {
|
|
1445
|
+
const deps = yield* SubAccountsDepsTag;
|
|
1446
|
+
const renamed = yield* deps.subAccountPort.rename({
|
|
1447
|
+
subAccountId,
|
|
1448
|
+
name
|
|
1449
|
+
}).pipe(Effect.tapError((error) => Effect.promise(() => emitSubAccountOpFailed(deps.telemetry, "rename", error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1450
|
+
yield* Effect.promise(() => emitSubAccountRenamed(deps.telemetry, renamed.id));
|
|
1451
|
+
return renamed;
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
function deleteSubAccountProgram(subAccountId) {
|
|
1455
|
+
return Effect.gen(function* () {
|
|
1456
|
+
const deps = yield* SubAccountsDepsTag;
|
|
1457
|
+
yield* deps.subAccountPort.delete({ subAccountId }).pipe(Effect.tapError((error) => Effect.promise(() => emitSubAccountOpFailed(deps.telemetry, "delete", error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1458
|
+
yield* Effect.promise(() => emitSubAccountDeleted(deps.telemetry, subAccountId));
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
function transferSubAccountProgram(input) {
|
|
1462
|
+
return Effect.gen(function* () {
|
|
1463
|
+
const deps = yield* SubAccountsDepsTag;
|
|
1464
|
+
yield* Effect.promise(() => emitTransferRequested(deps.telemetry, {
|
|
1465
|
+
amount: input.amount,
|
|
1466
|
+
from: input.from,
|
|
1467
|
+
to: input.to
|
|
1468
|
+
}));
|
|
1469
|
+
return yield* deps.subAccountPort.transfer(input).pipe(Effect.tapError((error) => Effect.promise(() => emitTransferFailed(deps.telemetry, error.publicCode))), Effect.mapError((error) => error.publicError));
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
//#endregion
|
|
1473
|
+
//#region src/client/sub-accounts.ts
|
|
1474
|
+
function makeSubAccountsMethods(deps) {
|
|
1475
|
+
const layer = subAccountsDepsLayer(deps);
|
|
1476
|
+
return {
|
|
1477
|
+
async create(accountId, input, options) {
|
|
1478
|
+
if (options?.signal?.aborted) return {
|
|
1479
|
+
ok: false,
|
|
1480
|
+
error: Errors.cancelled({ operation: "subAccounts.create" })
|
|
1481
|
+
};
|
|
1482
|
+
return toCapxulResult(createSubAccountProgram(accountId, input.name), layer);
|
|
1483
|
+
},
|
|
1484
|
+
async get(subAccountId, options) {
|
|
1485
|
+
if (options?.signal?.aborted) return {
|
|
1486
|
+
ok: false,
|
|
1487
|
+
error: Errors.cancelled({ operation: "subAccounts.get" })
|
|
1488
|
+
};
|
|
1489
|
+
return toCapxulResult(getSubAccountProgram(subAccountId), layer);
|
|
1490
|
+
},
|
|
1491
|
+
async list(accountId, options) {
|
|
1492
|
+
if (options?.signal?.aborted) return {
|
|
1493
|
+
ok: false,
|
|
1494
|
+
error: Errors.cancelled({ operation: "subAccounts.list" })
|
|
1495
|
+
};
|
|
1496
|
+
return toCapxulResult(listSubAccountsProgram(accountId), layer);
|
|
1497
|
+
},
|
|
1498
|
+
async rename(subAccountId, name, options) {
|
|
1499
|
+
if (options?.signal?.aborted) return {
|
|
1500
|
+
ok: false,
|
|
1501
|
+
error: Errors.cancelled({ operation: "subAccounts.rename" })
|
|
1502
|
+
};
|
|
1503
|
+
return toCapxulResult(renameSubAccountProgram(subAccountId, name), layer);
|
|
1504
|
+
},
|
|
1505
|
+
async delete(subAccountId, options) {
|
|
1506
|
+
if (options?.signal?.aborted) return {
|
|
1507
|
+
ok: false,
|
|
1508
|
+
error: Errors.cancelled({ operation: "subAccounts.delete" })
|
|
1509
|
+
};
|
|
1510
|
+
return toCapxulResult(deleteSubAccountProgram(subAccountId), layer);
|
|
1511
|
+
},
|
|
1512
|
+
async transfer(input, options) {
|
|
1513
|
+
if (options?.signal?.aborted) return {
|
|
1514
|
+
ok: false,
|
|
1515
|
+
error: Errors.cancelled({ operation: "subAccounts.transfer" })
|
|
1516
|
+
};
|
|
1517
|
+
return toCapxulResult(transferSubAccountProgram(input), layer);
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/money/from-wei.ts
|
|
1523
|
+
/**
|
|
1524
|
+
* Lift an on-chain integer (e.g. USDX 6-decimal) into consumer `Money`.
|
|
1525
|
+
* Canon `account-balance-model.md` §10 — internal at the SDK boundary only.
|
|
1526
|
+
*/
|
|
1527
|
+
function fromWei(rawBalance, decimals, currency) {
|
|
1528
|
+
if (!Number.isInteger(decimals) || decimals < 0) throw new Error(`fromWei: decimals must be a non-negative integer, got ${String(decimals)}`);
|
|
1529
|
+
const normalized = rawBalance.trim();
|
|
1530
|
+
if (!/^\d+$/.test(normalized)) {
|
|
1531
|
+
const truncated = normalized.length > 80 ? `${normalized.slice(0, 80)}…` : normalized;
|
|
1532
|
+
throw new Error(`fromWei: rawBalance must be a base-10 integer string, got ${truncated}`);
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
currency: toCurrencyCode(currency),
|
|
1536
|
+
value: formatUnits(BigInt(normalized), decimals),
|
|
1537
|
+
decimals
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/client/_shared/org-telemetry.ts
|
|
1542
|
+
/** Extract the domain from an email for telemetry (never the local part / PII). */
|
|
1543
|
+
function emailDomain(email) {
|
|
1544
|
+
const at = email.lastIndexOf("@");
|
|
1545
|
+
if (at < 0 || at === email.length - 1) return void 0;
|
|
1546
|
+
return email.slice(at + 1).toLowerCase();
|
|
1547
|
+
}
|
|
1548
|
+
/** Fire-and-forget emit through an optional port; never throws. */
|
|
1549
|
+
async function emitOrgTelemetry(telemetry, event) {
|
|
1550
|
+
if (telemetry === void 0) return;
|
|
1551
|
+
await Effect.runPromise(telemetry.emit(event).pipe(Effect.catchAll((cause) => Effect.sync(() => reportOrgTelemetryFailure("error", event.name, cause))), Effect.catchAllDefect((cause) => Effect.sync(() => reportOrgTelemetryFailure("defect", event.name, cause)))));
|
|
1552
|
+
}
|
|
1553
|
+
function emitOrgCreateStarted(telemetry, input) {
|
|
1554
|
+
const domain = input.email === void 0 ? void 0 : emailDomain(input.email);
|
|
1555
|
+
return emitOrgTelemetry(telemetry, {
|
|
1556
|
+
name: "org_create_started",
|
|
1557
|
+
props: {
|
|
1558
|
+
template: input.template,
|
|
1559
|
+
...domain === void 0 ? {} : { email_domain: domain }
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
/** Spread the `chainId` prop only when known — never emit a misleading `0`. */
|
|
1564
|
+
function chainIdProp(chainId) {
|
|
1565
|
+
return chainId === void 0 ? {} : { chainId };
|
|
1566
|
+
}
|
|
1567
|
+
function emitOrgSafeCreated(telemetry, input) {
|
|
1568
|
+
return emitOrgTelemetry(telemetry, {
|
|
1569
|
+
name: "org_safe_created",
|
|
1570
|
+
props: {
|
|
1571
|
+
org_id: input.orgId,
|
|
1572
|
+
safe_address: input.safeAddress,
|
|
1573
|
+
...chainIdProp(input.chainId)
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
function emitOrgSafeConfirmed(telemetry, input) {
|
|
1578
|
+
return emitOrgTelemetry(telemetry, {
|
|
1579
|
+
name: "org_safe_confirmed",
|
|
1580
|
+
props: {
|
|
1581
|
+
org_id: input.orgId,
|
|
1582
|
+
safe_address: input.safeAddress,
|
|
1583
|
+
...chainIdProp(input.chainId)
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
function emitOrgRolesSeeded(telemetry, input) {
|
|
1588
|
+
return emitOrgTelemetry(telemetry, {
|
|
1589
|
+
name: "org_roles_seeded",
|
|
1590
|
+
props: {
|
|
1591
|
+
org_id: input.orgId,
|
|
1592
|
+
role: input.role
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
function emitOrgCreated(telemetry, input) {
|
|
1597
|
+
return emitOrgTelemetry(telemetry, {
|
|
1598
|
+
name: "org_created",
|
|
1599
|
+
props: {
|
|
1600
|
+
org_id: input.orgId,
|
|
1601
|
+
safe_address: input.safeAddress,
|
|
1602
|
+
...chainIdProp(input.chainId)
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
function emitOrgCreateFailed(telemetry, reason) {
|
|
1607
|
+
return emitOrgTelemetry(telemetry, {
|
|
1608
|
+
name: "org_create_failed",
|
|
1609
|
+
props: { reason }
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
function emitOrgInviteSent(telemetry, input) {
|
|
1613
|
+
const domain = emailDomain(input.email);
|
|
1614
|
+
return emitOrgTelemetry(telemetry, {
|
|
1615
|
+
name: "org_invite_sent",
|
|
1616
|
+
props: {
|
|
1617
|
+
org_id: input.orgId,
|
|
1618
|
+
...domain === void 0 ? {} : { email_domain: domain },
|
|
1619
|
+
role: input.role,
|
|
1620
|
+
status: input.status
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
function orgSpendProps(input) {
|
|
1625
|
+
return {
|
|
1626
|
+
org_id: input.orgId,
|
|
1627
|
+
amount: input.amountRaw,
|
|
1628
|
+
currency: input.currency,
|
|
1629
|
+
sub_account_id: input.subAccountId,
|
|
1630
|
+
recipient: input.recipient,
|
|
1631
|
+
...input.role === void 0 ? {} : { role: input.role },
|
|
1632
|
+
...input.reason === void 0 ? {} : { reason: input.reason },
|
|
1633
|
+
...input.txHash === void 0 ? {} : { txHash: input.txHash }
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function emitOrgSpendStarted(telemetry, input) {
|
|
1637
|
+
return emitOrgTelemetry(telemetry, {
|
|
1638
|
+
name: "org_spend_started",
|
|
1639
|
+
props: orgSpendProps(input)
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
function emitOrgSpendAuthorized(telemetry, input) {
|
|
1643
|
+
return emitOrgTelemetry(telemetry, {
|
|
1644
|
+
name: "org_spend_authorized",
|
|
1645
|
+
props: orgSpendProps(input)
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
function emitOrgSpendSubmitted(telemetry, input) {
|
|
1649
|
+
return emitOrgTelemetry(telemetry, {
|
|
1650
|
+
name: "org_spend_submitted",
|
|
1651
|
+
props: orgSpendProps(input)
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
function emitOrgSpendConfirmed(telemetry, input) {
|
|
1655
|
+
return emitOrgTelemetry(telemetry, {
|
|
1656
|
+
name: "org_spend_confirmed",
|
|
1657
|
+
props: orgSpendProps(input)
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
function emitOrgSpendRejected(telemetry, input) {
|
|
1661
|
+
return emitOrgTelemetry(telemetry, {
|
|
1662
|
+
name: "org_spend_rejected",
|
|
1663
|
+
props: orgSpendProps(input)
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
function reportOrgTelemetryFailure(kind, name, cause) {
|
|
1667
|
+
if (!isOrgTelemetryDebugEnabled()) return;
|
|
1668
|
+
globalThis.console?.warn?.("[capxul] org telemetry dropped", {
|
|
1669
|
+
kind,
|
|
1670
|
+
name,
|
|
1671
|
+
cause
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
function isOrgTelemetryDebugEnabled() {
|
|
1675
|
+
return globalThis.process?.env?.CAPXUL_DEBUG_TELEMETRY === "1";
|
|
1676
|
+
}
|
|
1677
|
+
//#endregion
|
|
1678
|
+
//#region src/client/org-spend-gate.ts
|
|
1679
|
+
function evaluateSpendGate(input) {
|
|
1680
|
+
if (!input.activeMember) return reject("not_member", Errors.invalidInput("member", "you are not an active member"));
|
|
1681
|
+
if (!recipientAllowed(input.recipients, input.recipient)) return reject("invalid_recipient", Errors.invalidRecipient("recipient is not allowed by this role"));
|
|
1682
|
+
if (!subAccountAllowed(input.subAccounts, input.subAccountId)) return reject("wrong_envelope", Errors.invalidInput("subAccountId", "sub-account not in scope for this role"));
|
|
1683
|
+
const amount = parseRaw(input.amountRaw, "amountRaw");
|
|
1684
|
+
const balance = parseRaw(input.subAccountBalanceRaw, "subAccountBalanceRaw");
|
|
1685
|
+
if (amount > balance) return reject("insufficient_subaccount_balance", Errors.insufficientBalance(input.currency, balance.toString(10), amount.toString(10)));
|
|
1686
|
+
const perTxCap = parseOptionalRaw(input.perTxCapRaw, "perTxCapRaw");
|
|
1687
|
+
if (perTxCap !== null && amount > perTxCap) return reject("over_cap", Errors.insufficientBalance("role per-transaction cap", perTxCap.toString(10), amount.toString(10)));
|
|
1688
|
+
const perDayCap = parseOptionalRaw(input.perDayCapRaw, "perDayCapRaw");
|
|
1689
|
+
if (perDayCap !== null) {
|
|
1690
|
+
const remaining = perDayCap - (parseOptionalRaw(input.spentTodayRaw, "spentTodayRaw") ?? 0n);
|
|
1691
|
+
if (remaining < amount) return reject("daily_cap_exhausted", Errors.insufficientBalance("role daily cap", remaining.toString(10), amount.toString(10)));
|
|
1692
|
+
}
|
|
1693
|
+
return { ok: true };
|
|
1694
|
+
}
|
|
1695
|
+
function reject(reason, error) {
|
|
1696
|
+
return {
|
|
1697
|
+
ok: false,
|
|
1698
|
+
reason,
|
|
1699
|
+
error
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function recipientAllowed(recipients, recipient) {
|
|
1703
|
+
if (recipients === "anyone") return true;
|
|
1704
|
+
const normalized = recipient.toLowerCase();
|
|
1705
|
+
return recipients.some((allowed) => allowed.toLowerCase() === normalized);
|
|
1706
|
+
}
|
|
1707
|
+
function subAccountAllowed(scope, subAccountId) {
|
|
1708
|
+
if (scope.scope === "all") return true;
|
|
1709
|
+
return scope.subAccountIds.includes(subAccountId);
|
|
1710
|
+
}
|
|
1711
|
+
function parseOptionalRaw(value, field) {
|
|
1712
|
+
if (value === null || value === void 0) return null;
|
|
1713
|
+
return parseRaw(value, field);
|
|
1714
|
+
}
|
|
1715
|
+
function parseRaw(value, field) {
|
|
1716
|
+
if (!/^[0-9]+$/.test(value)) throw Errors.invalidInput(field, "must be a non-negative integer string");
|
|
1717
|
+
return BigInt(value);
|
|
1718
|
+
}
|
|
1719
|
+
//#endregion
|
|
1720
|
+
//#region src/client/org-deps.ts
|
|
1721
|
+
var OrgDepsTag = class extends Context.Tag("@capxul/sdk/OrgDeps")() {};
|
|
1722
|
+
function orgDepsLayer(deps) {
|
|
1723
|
+
return Layer.succeed(OrgDepsTag, deps);
|
|
1724
|
+
}
|
|
1725
|
+
function isHermeticOrgDeps(deps) {
|
|
1726
|
+
return deps.orgPort === void 0 && deps.orgRolesDeploymentPort === void 0 && deps.orgSpendPort === void 0;
|
|
1727
|
+
}
|
|
1728
|
+
function missingLiveOrgDep(portName) {
|
|
1729
|
+
return Errors.invalidInput("orgDeps", `${portName} is required in live org mode`);
|
|
1730
|
+
}
|
|
1731
|
+
/** Zero `Money` in the USDX-backed display currency (a fresh Org's treasury). */
|
|
1732
|
+
function zeroMoney() {
|
|
1733
|
+
return fromWei("0", USDX_DECIMALS, USDX_CURRENCY);
|
|
1734
|
+
}
|
|
1735
|
+
/** A fresh Org's treasury Account: `$0` balance, `available === balance`. */
|
|
1736
|
+
function zeroTreasury(orgId) {
|
|
1737
|
+
const balance = zeroMoney();
|
|
1738
|
+
return {
|
|
1739
|
+
id: toAccountId(`account_${stripPrefix(orgId)}`),
|
|
1740
|
+
balance,
|
|
1741
|
+
available: balance
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
/** Drop a known `<prefix>_` so the remainder is reusable as another brand seed. */
|
|
1745
|
+
function stripPrefix(value) {
|
|
1746
|
+
const underscore = value.indexOf("_");
|
|
1747
|
+
const cleaned = (underscore < 0 ? value : value.slice(underscore + 1)).replace(/[^0-9A-Za-z]/g, "");
|
|
1748
|
+
return cleaned.length > 0 ? cleaned : "0";
|
|
1749
|
+
}
|
|
1750
|
+
/** Map an arbitrary string deterministically into lowercase hex chars. */
|
|
1751
|
+
function toHexSeed(value) {
|
|
1752
|
+
let out = "";
|
|
1753
|
+
for (const ch of value) out += (ch.codePointAt(0) ?? 0).toString(16).padStart(2, "0");
|
|
1754
|
+
return out.length > 0 ? out : "0";
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Build the hermetic `OrgView` for the no-port L1 path: a deterministic
|
|
1758
|
+
* counterfactual-shaped placeholder Org Safe + a `$0` treasury (D3). The live
|
|
1759
|
+
* path replaces this with the real deployed address + RPC balance.
|
|
1760
|
+
*/
|
|
1761
|
+
function hermeticOrgView(input) {
|
|
1762
|
+
const orgId = toOrgId(`org_${stripPrefix(`x_${input.handle}`)}`);
|
|
1763
|
+
const placeholder = `0x${toHexSeed(input.handle).padEnd(40, "0").slice(0, 40)}`;
|
|
1764
|
+
return {
|
|
1765
|
+
id: orgId,
|
|
1766
|
+
name: input.name,
|
|
1767
|
+
handle: input.handle,
|
|
1768
|
+
safeAddress: toAddress(placeholder),
|
|
1769
|
+
role: "Owner",
|
|
1770
|
+
treasury: zeroTreasury(orgId)
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
function moneyFromConfig(input) {
|
|
1774
|
+
return {
|
|
1775
|
+
currency: toCurrencyCode(input.currency),
|
|
1776
|
+
value: input.value,
|
|
1777
|
+
decimals: input.decimals
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function recipientsFromConfig(toRecipients) {
|
|
1781
|
+
if (toRecipients === void 0 || toRecipients === "anyone") return toRecipients;
|
|
1782
|
+
return toRecipients.map((recipient) => toAddress(recipient));
|
|
1783
|
+
}
|
|
1784
|
+
function subAccountsFromConfig(subAccounts) {
|
|
1785
|
+
if (subAccounts === void 0) return void 0;
|
|
1786
|
+
if (subAccounts.scope === "all") return { scope: "all" };
|
|
1787
|
+
return { scope: subAccounts.scope.map((subAccountId) => toSubAccountId(subAccountId)) };
|
|
1788
|
+
}
|
|
1789
|
+
function roleDefinitionFromConfig(definition) {
|
|
1790
|
+
const toRecipients = recipientsFromConfig(definition.spend?.toRecipients);
|
|
1791
|
+
const subAccounts = subAccountsFromConfig(definition.subAccounts);
|
|
1792
|
+
return {
|
|
1793
|
+
label: definition.label,
|
|
1794
|
+
...definition.spend === void 0 ? {} : { spend: {
|
|
1795
|
+
...definition.spend.perTx === void 0 ? {} : { perTx: moneyFromConfig(definition.spend.perTx) },
|
|
1796
|
+
...definition.spend.perDay === void 0 ? {} : { perDay: moneyFromConfig(definition.spend.perDay) },
|
|
1797
|
+
...toRecipients === void 0 ? {} : { toRecipients }
|
|
1798
|
+
} },
|
|
1799
|
+
...subAccounts === void 0 ? {} : { subAccounts },
|
|
1800
|
+
...definition.canManageMembers === void 0 ? {} : { canManageMembers: definition.canManageMembers },
|
|
1801
|
+
...definition.canManageRoles === void 0 ? {} : { canManageRoles: definition.canManageRoles }
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function startupRoleViews(orgId) {
|
|
1805
|
+
return compileOrgRoleDefinitions(orgRoleTemplateDefinitions("Startup")).roles.map((role) => ({
|
|
1806
|
+
orgId,
|
|
1807
|
+
label: role.label,
|
|
1808
|
+
roleKey: toRoleKey(role.roleKey),
|
|
1809
|
+
definition: roleDefinitionFromConfig(role.definition)
|
|
1810
|
+
}));
|
|
1811
|
+
}
|
|
1812
|
+
function hermeticMember(input) {
|
|
1813
|
+
return {
|
|
1814
|
+
orgId: input.orgId,
|
|
1815
|
+
email: toEmail(input.email),
|
|
1816
|
+
name: null,
|
|
1817
|
+
personalSafeAddress: input.personalSafeAddress ?? null,
|
|
1818
|
+
role: input.role,
|
|
1819
|
+
roleKey: toRoleKey(orgRoleKeyForLabel(input.role)),
|
|
1820
|
+
status: input.status,
|
|
1821
|
+
grantTxHash: input.grantTxHash ?? null,
|
|
1822
|
+
revokeTxHash: input.revokeTxHash ?? null
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function hermeticSpendAuthority(input) {
|
|
1826
|
+
return {
|
|
1827
|
+
activeMember: true,
|
|
1828
|
+
subAccountBalanceRaw: "100000000000",
|
|
1829
|
+
recipients: "anyone",
|
|
1830
|
+
subAccounts: {
|
|
1831
|
+
scope: "only",
|
|
1832
|
+
subAccountIds: [input.from]
|
|
1833
|
+
},
|
|
1834
|
+
perTxCapRaw: "25000000000",
|
|
1835
|
+
perDayCapRaw: "100000000000",
|
|
1836
|
+
spentTodayRaw: "0",
|
|
1837
|
+
role: "Finance Manager"
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function hermeticSpendResult(orgId, input) {
|
|
1841
|
+
return { txHash: `0x${toHexSeed(`${orgId}:${input.from}:${input.to}:${input.amount.value}`).padEnd(64, "0").slice(0, 64)}` };
|
|
1842
|
+
}
|
|
1843
|
+
function spendTelemetryInput(input) {
|
|
1844
|
+
return {
|
|
1845
|
+
orgId: input.orgId,
|
|
1846
|
+
amountRaw: input.amountRaw,
|
|
1847
|
+
currency: input.currency,
|
|
1848
|
+
subAccountId: input.spend.from,
|
|
1849
|
+
recipient: input.spend.to,
|
|
1850
|
+
...input.authority?.role === void 0 ? {} : { role: input.authority.role },
|
|
1851
|
+
...input.reason === void 0 ? {} : { reason: input.reason },
|
|
1852
|
+
...input.txHash === void 0 ? {} : { txHash: input.txHash }
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function spendGateInput(input) {
|
|
1856
|
+
return {
|
|
1857
|
+
activeMember: input.authority.activeMember,
|
|
1858
|
+
subAccountId: input.spend.from,
|
|
1859
|
+
subAccountBalanceRaw: input.authority.subAccountBalanceRaw,
|
|
1860
|
+
amountRaw: input.amountRaw,
|
|
1861
|
+
currency: input.currency,
|
|
1862
|
+
recipient: input.spend.to,
|
|
1863
|
+
recipients: input.authority.recipients,
|
|
1864
|
+
subAccounts: input.authority.subAccounts,
|
|
1865
|
+
...input.authority.perTxCapRaw === void 0 ? {} : { perTxCapRaw: input.authority.perTxCapRaw },
|
|
1866
|
+
...input.authority.perDayCapRaw === void 0 ? {} : { perDayCapRaw: input.authority.perDayCapRaw },
|
|
1867
|
+
...input.authority.spentTodayRaw === void 0 ? {} : { spentTodayRaw: input.authority.spentTodayRaw }
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function createOrgProgram(input) {
|
|
1871
|
+
return Effect.gen(function* () {
|
|
1872
|
+
const deps = yield* OrgDepsTag;
|
|
1873
|
+
const chainId = deps.chainId;
|
|
1874
|
+
yield* Effect.promise(() => emitOrgCreateStarted(deps.telemetry, { template: input.template }));
|
|
1875
|
+
return yield* Effect.gen(function* () {
|
|
1876
|
+
if (deps.orgPort !== void 0) {
|
|
1877
|
+
const org = yield* deps.orgPort.createOrg({ input }).pipe(Effect.mapError((error) => error.publicError));
|
|
1878
|
+
yield* emitCreationFunnelTail(deps.telemetry, org, chainId);
|
|
1879
|
+
return org;
|
|
1880
|
+
}
|
|
1881
|
+
const org = hermeticOrgView(input);
|
|
1882
|
+
yield* emitCreationFunnelTail(deps.telemetry, org, chainId);
|
|
1883
|
+
return org;
|
|
1884
|
+
}).pipe(Effect.tapError((error) => Effect.promise(() => emitOrgCreateFailed(deps.telemetry, error.code))));
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
/** Emit the remaining `org_creation` funnel steps once the org is built. */
|
|
1888
|
+
function emitCreationFunnelTail(telemetry, org, chainId) {
|
|
1889
|
+
return Effect.promise(async () => {
|
|
1890
|
+
const chain = chainId === void 0 ? {} : { chainId };
|
|
1891
|
+
await emitOrgSafeCreated(telemetry, {
|
|
1892
|
+
orgId: org.id,
|
|
1893
|
+
safeAddress: org.safeAddress,
|
|
1894
|
+
...chain
|
|
1895
|
+
});
|
|
1896
|
+
await emitOrgSafeConfirmed(telemetry, {
|
|
1897
|
+
orgId: org.id,
|
|
1898
|
+
safeAddress: org.safeAddress,
|
|
1899
|
+
...chain
|
|
1900
|
+
});
|
|
1901
|
+
await emitOrgRolesSeeded(telemetry, {
|
|
1902
|
+
orgId: org.id,
|
|
1903
|
+
role: org.role
|
|
1904
|
+
});
|
|
1905
|
+
await emitOrgCreated(telemetry, {
|
|
1906
|
+
orgId: org.id,
|
|
1907
|
+
safeAddress: org.safeAddress,
|
|
1908
|
+
...chain
|
|
1909
|
+
});
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
function readTreasuryProgram(orgId) {
|
|
1913
|
+
return Effect.gen(function* () {
|
|
1914
|
+
const deps = yield* OrgDepsTag;
|
|
1915
|
+
if (deps.orgPort !== void 0) return yield* deps.orgPort.readTreasury({ orgId }).pipe(Effect.mapError((error) => error.publicError));
|
|
1916
|
+
return zeroTreasury(orgId);
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function listRolesProgram(orgId) {
|
|
1920
|
+
return Effect.gen(function* () {
|
|
1921
|
+
const deps = yield* OrgDepsTag;
|
|
1922
|
+
if (deps.orgPort !== void 0) return yield* deps.orgPort.listRoles({ orgId }).pipe(Effect.mapError((error) => error.publicError));
|
|
1923
|
+
return startupRoleViews(orgId);
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function deployRolesProgram(orgId) {
|
|
1927
|
+
return Effect.gen(function* () {
|
|
1928
|
+
const deps = yield* OrgDepsTag;
|
|
1929
|
+
if (deps.orgRolesDeploymentPort !== void 0) return (yield* deps.orgRolesDeploymentPort.deployRoles({ orgId }).pipe(Effect.mapError((error) => error.publicError))).roles;
|
|
1930
|
+
if (!isHermeticOrgDeps(deps)) return yield* Effect.fail(missingLiveOrgDep("orgRolesDeploymentPort"));
|
|
1931
|
+
return startupRoleViews(orgId);
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
function listMembersProgram(orgId) {
|
|
1935
|
+
return Effect.gen(function* () {
|
|
1936
|
+
const deps = yield* OrgDepsTag;
|
|
1937
|
+
if (deps.orgPort !== void 0) return yield* deps.orgPort.listMembers({ orgId }).pipe(Effect.mapError((error) => error.publicError));
|
|
1938
|
+
return [];
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
function inviteMemberProgram(orgId, input) {
|
|
1942
|
+
return Effect.gen(function* () {
|
|
1943
|
+
const deps = yield* OrgDepsTag;
|
|
1944
|
+
const member = deps.orgPort !== void 0 ? yield* deps.orgPort.inviteMember({
|
|
1945
|
+
orgId,
|
|
1946
|
+
input
|
|
1947
|
+
}).pipe(Effect.mapError((error) => error.publicError)) : hermeticMember({
|
|
1948
|
+
orgId,
|
|
1949
|
+
email: input.email,
|
|
1950
|
+
role: input.role,
|
|
1951
|
+
status: "pending"
|
|
1952
|
+
});
|
|
1953
|
+
yield* Effect.promise(() => emitOrgInviteSent(deps.telemetry, {
|
|
1954
|
+
orgId,
|
|
1955
|
+
email: input.email,
|
|
1956
|
+
role: member.role,
|
|
1957
|
+
status: member.status
|
|
1958
|
+
}));
|
|
1959
|
+
return member;
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
function detectAndAcceptPendingInvitationsProgram() {
|
|
1963
|
+
return Effect.gen(function* () {
|
|
1964
|
+
const deps = yield* OrgDepsTag;
|
|
1965
|
+
if (deps.orgPort !== void 0) return yield* deps.orgPort.detectAndAcceptPendingInvitations({}).pipe(Effect.mapError((error) => error.publicError));
|
|
1966
|
+
return { matched: [] };
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
function assignRoleProgram(orgId, input) {
|
|
1970
|
+
return Effect.gen(function* () {
|
|
1971
|
+
const deps = yield* OrgDepsTag;
|
|
1972
|
+
if (deps.orgRolesDeploymentPort !== void 0) return yield* deps.orgRolesDeploymentPort.grantRole({
|
|
1973
|
+
orgId,
|
|
1974
|
+
input
|
|
1975
|
+
}).pipe(Effect.mapError((error) => error.publicError));
|
|
1976
|
+
if (!isHermeticOrgDeps(deps)) return yield* Effect.fail(missingLiveOrgDep("orgRolesDeploymentPort"));
|
|
1977
|
+
return hermeticMember({
|
|
1978
|
+
orgId,
|
|
1979
|
+
email: "member@example.com",
|
|
1980
|
+
role: input.role,
|
|
1981
|
+
status: "active",
|
|
1982
|
+
personalSafeAddress: input.memberSafeAddress,
|
|
1983
|
+
grantTxHash: `0x${"0".repeat(64)}`
|
|
1984
|
+
});
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
function removeMemberProgram(orgId, input) {
|
|
1988
|
+
return Effect.gen(function* () {
|
|
1989
|
+
const deps = yield* OrgDepsTag;
|
|
1990
|
+
if (deps.orgRolesDeploymentPort !== void 0) return yield* deps.orgRolesDeploymentPort.revokeRole({
|
|
1991
|
+
orgId,
|
|
1992
|
+
input
|
|
1993
|
+
}).pipe(Effect.mapError((error) => error.publicError));
|
|
1994
|
+
if (!isHermeticOrgDeps(deps)) return yield* Effect.fail(missingLiveOrgDep("orgRolesDeploymentPort"));
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
function spendProgram(orgId, input) {
|
|
1998
|
+
return Effect.gen(function* () {
|
|
1999
|
+
const deps = yield* OrgDepsTag;
|
|
2000
|
+
const amountRaw = yield* Effect.try({
|
|
2001
|
+
try: () => toWei(input.amount),
|
|
2002
|
+
catch: (cause) => Errors.invalidInput("amount", cause instanceof Error ? cause.message : "invalid money")
|
|
2003
|
+
});
|
|
2004
|
+
const currency = String(input.amount.currency);
|
|
2005
|
+
yield* Effect.promise(() => emitOrgSpendStarted(deps.telemetry, {
|
|
2006
|
+
orgId,
|
|
2007
|
+
amountRaw,
|
|
2008
|
+
currency,
|
|
2009
|
+
subAccountId: input.from,
|
|
2010
|
+
recipient: input.to
|
|
2011
|
+
}));
|
|
2012
|
+
if (deps.orgSpendPort === void 0 && !isHermeticOrgDeps(deps)) return yield* Effect.fail(missingLiveOrgDep("orgSpendPort"));
|
|
2013
|
+
const authority = deps.orgSpendPort === void 0 ? hermeticSpendAuthority(input) : yield* deps.orgSpendPort.readSpendAuthority({
|
|
2014
|
+
orgId,
|
|
2015
|
+
input,
|
|
2016
|
+
amountRaw
|
|
2017
|
+
}).pipe(Effect.mapError((error) => error.publicError));
|
|
2018
|
+
const decision = yield* Effect.try({
|
|
2019
|
+
try: () => evaluateSpendGate(spendGateInput({
|
|
2020
|
+
authority,
|
|
2021
|
+
spend: input,
|
|
2022
|
+
amountRaw,
|
|
2023
|
+
currency
|
|
2024
|
+
})),
|
|
2025
|
+
catch: (cause) => isCapxulError(cause) ? cause : Errors.unknown(cause)
|
|
2026
|
+
});
|
|
2027
|
+
if (!decision.ok) {
|
|
2028
|
+
yield* Effect.promise(() => emitOrgSpendRejected(deps.telemetry, spendTelemetryInput({
|
|
2029
|
+
orgId,
|
|
2030
|
+
amountRaw,
|
|
2031
|
+
currency,
|
|
2032
|
+
spend: input,
|
|
2033
|
+
authority,
|
|
2034
|
+
reason: decision.reason
|
|
2035
|
+
})));
|
|
2036
|
+
return yield* Effect.fail(decision.error);
|
|
2037
|
+
}
|
|
2038
|
+
yield* Effect.promise(() => emitOrgSpendAuthorized(deps.telemetry, spendTelemetryInput({
|
|
2039
|
+
orgId,
|
|
2040
|
+
amountRaw,
|
|
2041
|
+
currency,
|
|
2042
|
+
spend: input,
|
|
2043
|
+
authority
|
|
2044
|
+
})));
|
|
2045
|
+
const result = deps.orgSpendPort === void 0 ? hermeticSpendResult(orgId, input) : yield* deps.orgSpendPort.submitSpend({
|
|
2046
|
+
orgId,
|
|
2047
|
+
input,
|
|
2048
|
+
amountRaw,
|
|
2049
|
+
authority
|
|
2050
|
+
}).pipe(Effect.mapError((error) => error.publicError), Effect.tapError((error) => Effect.promise(() => emitOrgSpendRejected(deps.telemetry, spendTelemetryInput({
|
|
2051
|
+
orgId,
|
|
2052
|
+
amountRaw,
|
|
2053
|
+
currency,
|
|
2054
|
+
spend: input,
|
|
2055
|
+
authority,
|
|
2056
|
+
reason: error.code
|
|
2057
|
+
})))));
|
|
2058
|
+
yield* Effect.promise(() => emitOrgSpendSubmitted(deps.telemetry, spendTelemetryInput({
|
|
2059
|
+
orgId,
|
|
2060
|
+
amountRaw,
|
|
2061
|
+
currency,
|
|
2062
|
+
spend: input,
|
|
2063
|
+
authority,
|
|
2064
|
+
txHash: result.txHash
|
|
2065
|
+
})));
|
|
2066
|
+
yield* Effect.promise(() => emitOrgSpendConfirmed(deps.telemetry, spendTelemetryInput({
|
|
2067
|
+
orgId,
|
|
2068
|
+
amountRaw,
|
|
2069
|
+
currency,
|
|
2070
|
+
spend: input,
|
|
2071
|
+
authority,
|
|
2072
|
+
txHash: result.txHash
|
|
2073
|
+
})));
|
|
2074
|
+
return result;
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
function listOrgsProgram() {
|
|
2078
|
+
return Effect.gen(function* () {
|
|
2079
|
+
const deps = yield* OrgDepsTag;
|
|
2080
|
+
if (deps.orgPort !== void 0) return yield* deps.orgPort.listOrgs({}).pipe(Effect.mapError((error) => error.publicError));
|
|
2081
|
+
return [];
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
//#endregion
|
|
2085
|
+
//#region src/client/org.ts
|
|
2086
|
+
/**
|
|
2087
|
+
* Org method surface (canon §C2/§C3, D13). S1 (#274) wires `createOrg` /
|
|
2088
|
+
* `orgs` / `treasury` to their Effect programs (`org-deps.ts`); the S2→S4
|
|
2089
|
+
* member/role/spend verbs stay `Errors.notImplemented("org", "<verb>")` until
|
|
2090
|
+
* their slices land. The entity-scoped bundle is constructed per `org(orgId)`
|
|
2091
|
+
* call (D13 — explicit scoping, no shared mutable "active org" state); the
|
|
2092
|
+
* closed-over `orgId` is the only entity reference.
|
|
2093
|
+
*/
|
|
2094
|
+
function makeOrgMethods(deps) {
|
|
2095
|
+
const layer = orgDepsLayer({
|
|
2096
|
+
...deps.orgPort === void 0 ? {} : { orgPort: deps.orgPort },
|
|
2097
|
+
...deps.orgRolesDeploymentPort === void 0 ? {} : { orgRolesDeploymentPort: deps.orgRolesDeploymentPort },
|
|
2098
|
+
...deps.orgSpendPort === void 0 ? {} : { orgSpendPort: deps.orgSpendPort },
|
|
2099
|
+
...deps.chainId === void 0 ? {} : { chainId: deps.chainId },
|
|
2100
|
+
...deps.telemetry === void 0 ? {} : { telemetry: deps.telemetry }
|
|
2101
|
+
});
|
|
2102
|
+
return {
|
|
2103
|
+
createOrg(input, options) {
|
|
2104
|
+
if (options?.signal?.aborted) return Promise.resolve({
|
|
2105
|
+
ok: false,
|
|
2106
|
+
error: Errors.cancelled({ operation: "org.createOrg" })
|
|
2107
|
+
});
|
|
2108
|
+
return toCapxulResult(createOrgProgram(input), layer);
|
|
2109
|
+
},
|
|
2110
|
+
orgs: Object.assign((options) => {
|
|
2111
|
+
if (options?.signal?.aborted) return Promise.resolve({
|
|
2112
|
+
ok: false,
|
|
2113
|
+
error: Errors.cancelled({ operation: "org.orgs" })
|
|
2114
|
+
});
|
|
2115
|
+
return toCapxulResult(listOrgsProgram(), layer);
|
|
2116
|
+
}, { detectAndAcceptPendingInvitations(options) {
|
|
2117
|
+
if (options?.signal?.aborted) return Promise.resolve({
|
|
2118
|
+
ok: false,
|
|
2119
|
+
error: Errors.cancelled({ operation: "orgs.detectAndAcceptPendingInvitations" })
|
|
2120
|
+
});
|
|
2121
|
+
return toCapxulResult(detectAndAcceptPendingInvitationsProgram(), layer);
|
|
2122
|
+
} }),
|
|
2123
|
+
org(orgId) {
|
|
2124
|
+
return {
|
|
2125
|
+
treasury(options) {
|
|
2126
|
+
if (options?.signal?.aborted) return Promise.resolve({
|
|
2127
|
+
ok: false,
|
|
2128
|
+
error: Errors.cancelled({ operation: "org.treasury" })
|
|
2129
|
+
});
|
|
2130
|
+
return toCapxulResult(readTreasuryProgram(orgId), layer);
|
|
2131
|
+
},
|
|
2132
|
+
members(_options) {
|
|
2133
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2134
|
+
ok: false,
|
|
2135
|
+
error: Errors.cancelled({ operation: "org.members" })
|
|
2136
|
+
});
|
|
2137
|
+
return toCapxulResult(listMembersProgram(orgId), layer);
|
|
2138
|
+
},
|
|
2139
|
+
roles(_options) {
|
|
2140
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2141
|
+
ok: false,
|
|
2142
|
+
error: Errors.cancelled({ operation: "org.roles" })
|
|
2143
|
+
});
|
|
2144
|
+
return toCapxulResult(listRolesProgram(orgId), layer);
|
|
2145
|
+
},
|
|
2146
|
+
deployRoles(_options) {
|
|
2147
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2148
|
+
ok: false,
|
|
2149
|
+
error: Errors.cancelled({ operation: "org.deployRoles" })
|
|
2150
|
+
});
|
|
2151
|
+
return toCapxulResult(deployRolesProgram(orgId), layer);
|
|
2152
|
+
},
|
|
2153
|
+
invite(_input, _options) {
|
|
2154
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2155
|
+
ok: false,
|
|
2156
|
+
error: Errors.cancelled({ operation: "org.invite" })
|
|
2157
|
+
});
|
|
2158
|
+
return toCapxulResult(inviteMemberProgram(orgId, _input), layer);
|
|
2159
|
+
},
|
|
2160
|
+
removeMember(_input, _options) {
|
|
2161
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2162
|
+
ok: false,
|
|
2163
|
+
error: Errors.cancelled({ operation: "org.removeMember" })
|
|
2164
|
+
});
|
|
2165
|
+
return toCapxulResult(removeMemberProgram(orgId, _input), layer);
|
|
2166
|
+
},
|
|
2167
|
+
assignRole(_input, _options) {
|
|
2168
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2169
|
+
ok: false,
|
|
2170
|
+
error: Errors.cancelled({ operation: "org.assignRole" })
|
|
2171
|
+
});
|
|
2172
|
+
return toCapxulResult(assignRoleProgram(orgId, _input), layer);
|
|
2173
|
+
},
|
|
2174
|
+
spend(_input, _options) {
|
|
2175
|
+
if (_options?.signal?.aborted) return Promise.resolve({
|
|
2176
|
+
ok: false,
|
|
2177
|
+
error: Errors.cancelled({ operation: "org.spend" })
|
|
2178
|
+
});
|
|
2179
|
+
return toCapxulResult(spendProgram(orgId, _input), layer);
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
//#endregion
|
|
2186
|
+
//#region src/client/factory.ts
|
|
2187
|
+
function detectAuthCacheAdapter() {
|
|
2188
|
+
const globalAny = globalThis;
|
|
2189
|
+
if (globalAny.window?.localStorage !== void 0) return new BrowserAuthCacheAdapter(globalAny.window.localStorage);
|
|
2190
|
+
return new InMemoryAuthCacheAdapter();
|
|
2191
|
+
}
|
|
2192
|
+
//#endregion
|
|
2193
|
+
//#region src/client/create-capxul-client.ts
|
|
2194
|
+
function assembleCapxulClient(input) {
|
|
2195
|
+
const authCache = input.authCache ?? detectAuthCacheAdapter();
|
|
2196
|
+
const layer = Layer.mergeAll(Layer.succeed(AuthClientPortTag, input.ports.authClient), Layer.succeed(ClockPortTag, input.ports.clock), Layer.succeed(TelemetryPortTag, input.ports.telemetry));
|
|
2197
|
+
const authFlowInput = input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs };
|
|
2198
|
+
const actor = Effect.runSync(bootAuthFlow(createAuthFlowMachine(), authFlowInput).pipe(Effect.provide(layer)));
|
|
2199
|
+
if (input.signal !== void 0) input.signal.addEventListener("abort", () => actor.stop(), { once: true });
|
|
2200
|
+
const selectedOrgPort = input.orgPort ?? input.orgDeploymentPort;
|
|
2201
|
+
let detectPendingOrgInvitations;
|
|
2202
|
+
const auth = makeAuthMethods({
|
|
2203
|
+
actor,
|
|
2204
|
+
authClient: input.ports.authClient,
|
|
2205
|
+
authCache,
|
|
2206
|
+
clock: input.ports.clock,
|
|
2207
|
+
telemetry: input.ports.telemetry,
|
|
2208
|
+
...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
|
|
2209
|
+
...input.invokeTimeoutMs === void 0 ? {} : { invokeTimeoutMs: input.invokeTimeoutMs },
|
|
2210
|
+
...selectedOrgPort === void 0 ? {} : { afterVerifyOtp: async () => {
|
|
2211
|
+
await detectPendingOrgInvitations?.();
|
|
2212
|
+
} }
|
|
2213
|
+
});
|
|
2214
|
+
const smartAccount = makeSmartAccountMethods({
|
|
2215
|
+
actor,
|
|
2216
|
+
smartAccountPort: input.ports.smartAccount,
|
|
2217
|
+
chainId: input.bootstrap.chainId,
|
|
2218
|
+
sessionAuthUserIdFallback: null,
|
|
2219
|
+
telemetry: input.ports.telemetry
|
|
2220
|
+
});
|
|
2221
|
+
const identity = makeIdentityMethods({
|
|
2222
|
+
identityPort: input.ports.identity,
|
|
2223
|
+
actor
|
|
2224
|
+
});
|
|
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
|
+
const accounts = makeAccountsMethods({
|
|
2236
|
+
accountReadPort: input.ports.accountRead,
|
|
2237
|
+
chainId: input.bootstrap.chainId,
|
|
2238
|
+
telemetry: input.ports.telemetry
|
|
2239
|
+
});
|
|
2240
|
+
const subAccounts = makeSubAccountsMethods({
|
|
2241
|
+
subAccountPort: input.ports.subAccount,
|
|
2242
|
+
telemetry: input.ports.telemetry
|
|
2243
|
+
});
|
|
2244
|
+
const orgMethods = makeOrgMethods({
|
|
2245
|
+
chainId: input.bootstrap.chainId,
|
|
2246
|
+
telemetry: input.ports.telemetry,
|
|
2247
|
+
...selectedOrgPort === void 0 ? {} : { orgPort: selectedOrgPort },
|
|
2248
|
+
...input.orgRolesDeploymentPort === void 0 ? {} : { orgRolesDeploymentPort: input.orgRolesDeploymentPort },
|
|
2249
|
+
...input.orgSpendPort === void 0 ? {} : { orgSpendPort: input.orgSpendPort }
|
|
2250
|
+
});
|
|
2251
|
+
if (selectedOrgPort !== void 0) detectPendingOrgInvitations = async () => {
|
|
2252
|
+
await orgMethods.orgs.detectAndAcceptPendingInvitations();
|
|
2253
|
+
};
|
|
2254
|
+
return {
|
|
2255
|
+
auth,
|
|
2256
|
+
smartAccount,
|
|
2257
|
+
identity,
|
|
2258
|
+
account,
|
|
2259
|
+
accounts,
|
|
2260
|
+
subAccounts,
|
|
2261
|
+
createOrg: orgMethods.createOrg,
|
|
2262
|
+
orgs: orgMethods.orgs,
|
|
2263
|
+
org: orgMethods.org,
|
|
2264
|
+
bootstrap: input.bootstrap,
|
|
2265
|
+
_internal: {
|
|
2266
|
+
authActor: {
|
|
2267
|
+
input: actor.input,
|
|
2268
|
+
getSnapshot: actor.getSnapshot,
|
|
2269
|
+
stop: actor.stop
|
|
2270
|
+
},
|
|
2271
|
+
accounts: { fund: accounts.fund }
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
//#endregion
|
|
2276
|
+
//#region src/client/account-providers.ts
|
|
2277
|
+
function localPrivateKeyAccountProvider(input) {
|
|
2278
|
+
const account = privateKeyToAccount(input.privateKey);
|
|
2279
|
+
const address = toAddress(account.address);
|
|
2280
|
+
return {
|
|
2281
|
+
source: "local-private-key",
|
|
2282
|
+
async getAddress() {
|
|
2283
|
+
return {
|
|
2284
|
+
ok: true,
|
|
2285
|
+
value: address
|
|
2286
|
+
};
|
|
2287
|
+
},
|
|
2288
|
+
async getDeployAccount() {
|
|
2289
|
+
return {
|
|
2290
|
+
ok: true,
|
|
2291
|
+
value: account
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function eip1193AccountProvider(input) {
|
|
2297
|
+
let cachedAddress = null;
|
|
2298
|
+
let inFlight = null;
|
|
2299
|
+
const getAddress = async () => {
|
|
2300
|
+
if (cachedAddress !== null) return {
|
|
2301
|
+
ok: true,
|
|
2302
|
+
value: cachedAddress
|
|
2303
|
+
};
|
|
2304
|
+
if (inFlight !== null) return inFlight;
|
|
2305
|
+
inFlight = (async () => {
|
|
2306
|
+
try {
|
|
2307
|
+
const first = firstAccount(await input.provider.request({ method: "eth_accounts" }));
|
|
2308
|
+
if (first === null) return {
|
|
2309
|
+
ok: false,
|
|
2310
|
+
error: Errors.smartAccountMissing("eip1193-account")
|
|
2311
|
+
};
|
|
2312
|
+
const address = toAddress(first);
|
|
2313
|
+
cachedAddress = address;
|
|
2314
|
+
return {
|
|
2315
|
+
ok: true,
|
|
2316
|
+
value: address
|
|
2317
|
+
};
|
|
2318
|
+
} catch (err) {
|
|
2319
|
+
return {
|
|
2320
|
+
ok: false,
|
|
2321
|
+
error: Errors.providerError("eip1193", "eth_accounts", err)
|
|
2322
|
+
};
|
|
2323
|
+
} finally {
|
|
2324
|
+
inFlight = null;
|
|
2325
|
+
}
|
|
2326
|
+
})();
|
|
2327
|
+
return inFlight;
|
|
2328
|
+
};
|
|
2329
|
+
return {
|
|
2330
|
+
source: "injected-eip1193",
|
|
2331
|
+
getAddress,
|
|
2332
|
+
async getDeployAccount() {
|
|
2333
|
+
return {
|
|
2334
|
+
ok: false,
|
|
2335
|
+
error: Errors.notImplemented("Eip1193AccountProvider", "getDeployAccount")
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function openfortEmbeddedAccountProvider() {
|
|
2341
|
+
return {
|
|
2342
|
+
source: "openfort-embedded",
|
|
2343
|
+
async getAddress() {
|
|
2344
|
+
return {
|
|
2345
|
+
ok: false,
|
|
2346
|
+
error: Errors.notImplemented("OpenfortAccountProvider", "getAddress")
|
|
2347
|
+
};
|
|
2348
|
+
},
|
|
2349
|
+
async getDeployAccount() {
|
|
2350
|
+
return {
|
|
2351
|
+
ok: false,
|
|
2352
|
+
error: Errors.notImplemented("OpenfortAccountProvider", "getDeployAccount")
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function firstAccount(value) {
|
|
2358
|
+
if (!Array.isArray(value)) return null;
|
|
2359
|
+
const first = value[0];
|
|
2360
|
+
return typeof first === "string" ? first : null;
|
|
2361
|
+
}
|
|
2362
|
+
//#endregion
|
|
2363
|
+
//#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}$/;
|
|
2366
|
+
/**
|
|
2367
|
+
* 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.
|
|
2371
|
+
*/
|
|
2372
|
+
function injectedWalletSigner(provider) {
|
|
2373
|
+
const resolveAddress = async () => {
|
|
2374
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2375
|
+
const first = Array.isArray(accounts) ? accounts[0] : void 0;
|
|
2376
|
+
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");
|
|
2378
|
+
return toAddress(first);
|
|
2379
|
+
};
|
|
2380
|
+
return {
|
|
2381
|
+
source: "injected-eip1193",
|
|
2382
|
+
getAddress: resolveAddress,
|
|
2383
|
+
async signUserOpHash(hash) {
|
|
2384
|
+
const address = await resolveAddress();
|
|
2385
|
+
let signature;
|
|
2386
|
+
try {
|
|
2387
|
+
signature = await provider.request({
|
|
2388
|
+
method: "eth_sign",
|
|
2389
|
+
params: [address, hash]
|
|
2390
|
+
});
|
|
2391
|
+
} catch (cause) {
|
|
2392
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
2393
|
+
throw new Error(`injectedWalletSigner: eth_sign failed; enable raw-hash signing for deployment (${detail})`);
|
|
2394
|
+
}
|
|
2395
|
+
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");
|
|
2397
|
+
return signature;
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
//#endregion
|
|
2402
|
+
//#region package.json
|
|
2403
|
+
var version = "0.2.0-alpha.5";
|
|
2404
|
+
//#endregion
|
|
2405
|
+
//#region src/adapters/auth-client/effect-port.ts
|
|
2406
|
+
function authClientPortFromPromiseAdapter(adapter) {
|
|
2407
|
+
return {
|
|
2408
|
+
canSendOtp: (input, options) => fromResult("canSendOtp", () => adapter.canSendOtp(input, options)),
|
|
2409
|
+
sendOtp: (input, options) => fromResult("sendOtp", () => adapter.sendOtp(input, options)),
|
|
2410
|
+
verifyOtp: (input, options) => fromResult("verifyOtp", () => adapter.verifyOtp(input, options)),
|
|
2411
|
+
getSession: (options) => fromResult("getSession", () => adapter.getSession(options)),
|
|
2412
|
+
signOut: (options) => fromResult("signOut", () => adapter.signOut(options)),
|
|
2413
|
+
getConvexJwt: (options) => fromResult("getConvexJwt", () => adapter.getConvexJwt(options))
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
function fromResult(operation, run) {
|
|
2417
|
+
return Effect.tryPromise({
|
|
2418
|
+
try: run,
|
|
2419
|
+
catch: (cause) => new AuthClientError({
|
|
2420
|
+
operation,
|
|
2421
|
+
kind: "thrown",
|
|
2422
|
+
cause
|
|
2423
|
+
})
|
|
2424
|
+
}).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(new AuthClientError({
|
|
2425
|
+
operation,
|
|
2426
|
+
kind: errorKind(result.error),
|
|
2427
|
+
cause: result.error
|
|
2428
|
+
}))));
|
|
2429
|
+
}
|
|
2430
|
+
function errorKind(error) {
|
|
2431
|
+
if (error.code === "CANCELLED") return "cancelled";
|
|
2432
|
+
if (error.code === "NETWORK_ERROR") return "network";
|
|
2433
|
+
if (error.code === "INVALID_INPUT" || error.code === "OTP_EXPIRED") return "validation";
|
|
2434
|
+
return "provider";
|
|
2435
|
+
}
|
|
2436
|
+
//#endregion
|
|
2437
|
+
//#region src/adapters/auth-client/resolve-auth-url.ts
|
|
2438
|
+
/** Join bootstrap `authBaseUrl` with a BetterAuth route without duplicating `/api/auth`. */
|
|
2439
|
+
function resolveAuthClientUrl(authBaseUrl, path) {
|
|
2440
|
+
const base = authBaseUrl.replace(/\/$/, "");
|
|
2441
|
+
if (base.endsWith("/api/auth") && path.startsWith("/api/auth")) return `${base}${path.slice(9)}`;
|
|
2442
|
+
return `${base}${path}`;
|
|
2443
|
+
}
|
|
2444
|
+
//#endregion
|
|
2445
|
+
//#region src/adapters/auth-client/BetterAuthBrowserAdapter.ts
|
|
2446
|
+
function withSignal$1(init, signal) {
|
|
2447
|
+
return signal === void 0 ? init : {
|
|
2448
|
+
...init,
|
|
2449
|
+
signal
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
const DEFAULT_JWT_LIFETIME_S$1 = 900;
|
|
2453
|
+
function decodeJwtExp$1(jwt) {
|
|
2454
|
+
const parts = jwt.split(".");
|
|
2455
|
+
if (parts.length < 2 || parts[1] === void 0) return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S$1;
|
|
2456
|
+
try {
|
|
2457
|
+
const raw = parts[1].replace(/\s+/g, "");
|
|
2458
|
+
const pad = "=".repeat((4 - raw.length % 4) % 4);
|
|
2459
|
+
const decoded = atob(raw.replace(/-/g, "+").replace(/_/g, "/") + pad);
|
|
2460
|
+
const payload = JSON.parse(decoded);
|
|
2461
|
+
if (typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > 0) return payload.exp;
|
|
2462
|
+
} catch {}
|
|
2463
|
+
return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S$1;
|
|
2464
|
+
}
|
|
2465
|
+
function authSessionFromBetterAuth$1(token, user) {
|
|
2466
|
+
return {
|
|
2467
|
+
authUserId: toAuthUserId(user.id),
|
|
2468
|
+
email: toEmail(user.email),
|
|
2469
|
+
token: toSessionToken(token),
|
|
2470
|
+
expiresAt: toEpochMs(Date.now() + 10080 * 60 * 1e3)
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
async function safeJson$1(res) {
|
|
2474
|
+
const raw = await res.text();
|
|
2475
|
+
if (raw.length === 0 || raw === "null") return null;
|
|
2476
|
+
try {
|
|
2477
|
+
return JSON.parse(raw);
|
|
2478
|
+
} catch {
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
function mapBetterAuthError(operation, body) {
|
|
2483
|
+
if (typeof body === "object" && body !== null) {
|
|
2484
|
+
const errBody = body;
|
|
2485
|
+
const code = typeof errBody.code === "string" ? errBody.code : "";
|
|
2486
|
+
if (code === "OTP_EXPIRED") return Errors.otpExpired();
|
|
2487
|
+
if (code === "INVALID_OTP") return Errors.invalidInput("otp", errBody.message ?? "invalid OTP");
|
|
2488
|
+
if (code === "VALIDATION_ERROR" || code === "INVALID_EMAIL") return Errors.invalidInput("email", errBody.message ?? "invalid email");
|
|
2489
|
+
}
|
|
2490
|
+
return Errors.providerError("better-auth", operation, new Error(String(body)));
|
|
2491
|
+
}
|
|
2492
|
+
function isAbortError$1(err, signal) {
|
|
2493
|
+
return signal?.aborted === true || err instanceof Error && err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError";
|
|
2494
|
+
}
|
|
2495
|
+
function mapFetchError$1(operation, err, signal) {
|
|
2496
|
+
if (isAbortError$1(err, signal)) return Errors.cancelled({ operation });
|
|
2497
|
+
return Errors.networkError(operation, err instanceof Error ? err : new Error(String(err)));
|
|
2498
|
+
}
|
|
2499
|
+
var BetterAuthBrowserAdapter = class {
|
|
2500
|
+
authBaseUrl;
|
|
2501
|
+
fetchImpl;
|
|
2502
|
+
constructor(deps) {
|
|
2503
|
+
this.authBaseUrl = deps.authBaseUrl.replace(/\/$/, "");
|
|
2504
|
+
this.fetchImpl = deps.fetch ?? fetch;
|
|
2505
|
+
}
|
|
2506
|
+
url(path) {
|
|
2507
|
+
return resolveAuthClientUrl(this.authBaseUrl, path);
|
|
2508
|
+
}
|
|
2509
|
+
async canSendOtp(_input, options) {
|
|
2510
|
+
if (options?.signal?.aborted) return {
|
|
2511
|
+
ok: false,
|
|
2512
|
+
error: Errors.cancelled({ operation: "canSendOtp" })
|
|
2513
|
+
};
|
|
2514
|
+
return {
|
|
2515
|
+
ok: true,
|
|
2516
|
+
value: {
|
|
2517
|
+
allowed: true,
|
|
2518
|
+
cooldownMs: toDurationMs(0)
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
async sendOtp(input, options) {
|
|
2523
|
+
if (options?.signal?.aborted) return {
|
|
2524
|
+
ok: false,
|
|
2525
|
+
error: Errors.cancelled({ operation: "sendOtp" })
|
|
2526
|
+
};
|
|
2527
|
+
try {
|
|
2528
|
+
const res = await this.fetchImpl(this.url("/api/auth/email-otp/send-verification-otp"), withSignal$1({
|
|
2529
|
+
method: "POST",
|
|
2530
|
+
headers: { "Content-Type": "application/json" },
|
|
2531
|
+
body: JSON.stringify({
|
|
2532
|
+
email: input.email,
|
|
2533
|
+
type: "sign-in"
|
|
2534
|
+
}),
|
|
2535
|
+
credentials: "include"
|
|
2536
|
+
}, options?.signal));
|
|
2537
|
+
if (options?.signal?.aborted) return {
|
|
2538
|
+
ok: false,
|
|
2539
|
+
error: Errors.cancelled({ operation: "sendOtp" })
|
|
2540
|
+
};
|
|
2541
|
+
if (res.ok) return {
|
|
2542
|
+
ok: true,
|
|
2543
|
+
value: void 0
|
|
2544
|
+
};
|
|
2545
|
+
if (res.status === 429) return {
|
|
2546
|
+
ok: false,
|
|
2547
|
+
error: Errors.rateLimited({ resource: "better-auth/sendOtp" })
|
|
2548
|
+
};
|
|
2549
|
+
const body = await safeJson$1(res);
|
|
2550
|
+
if (typeof body === "object" && body !== null) {
|
|
2551
|
+
const errBody = body;
|
|
2552
|
+
if (errBody.code === "INVALID_EMAIL" || errBody.code === "VALIDATION_ERROR") return {
|
|
2553
|
+
ok: false,
|
|
2554
|
+
error: Errors.invalidInput("email", errBody.message ?? "invalid email")
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
return {
|
|
2558
|
+
ok: false,
|
|
2559
|
+
error: Errors.providerError("better-auth", "sendOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
2560
|
+
};
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
return {
|
|
2563
|
+
ok: false,
|
|
2564
|
+
error: mapFetchError$1("sendOtp", err, options?.signal)
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
async verifyOtp(input, options) {
|
|
2569
|
+
if (options?.signal?.aborted) return {
|
|
2570
|
+
ok: false,
|
|
2571
|
+
error: Errors.cancelled({ operation: "verifyOtp" })
|
|
2572
|
+
};
|
|
2573
|
+
try {
|
|
2574
|
+
const res = await this.fetchImpl(this.url("/api/auth/sign-in/email-otp"), withSignal$1({
|
|
2575
|
+
method: "POST",
|
|
2576
|
+
headers: { "Content-Type": "application/json" },
|
|
2577
|
+
body: JSON.stringify({
|
|
2578
|
+
email: input.email,
|
|
2579
|
+
otp: input.otp
|
|
2580
|
+
}),
|
|
2581
|
+
credentials: "include"
|
|
2582
|
+
}, options?.signal));
|
|
2583
|
+
if (options?.signal?.aborted) return {
|
|
2584
|
+
ok: false,
|
|
2585
|
+
error: Errors.cancelled({ operation: "verifyOtp" })
|
|
2586
|
+
};
|
|
2587
|
+
const body = await safeJson$1(res);
|
|
2588
|
+
if (res.ok) {
|
|
2589
|
+
if (typeof body === "object" && body !== null) {
|
|
2590
|
+
const okBody = body;
|
|
2591
|
+
if (typeof okBody.token === "string" && typeof okBody.user === "object" && okBody.user !== null) return {
|
|
2592
|
+
ok: true,
|
|
2593
|
+
value: authSessionFromBetterAuth$1(okBody.token, okBody.user)
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
return {
|
|
2597
|
+
ok: false,
|
|
2598
|
+
error: Errors.providerError("better-auth", "verifyOtp", /* @__PURE__ */ new Error("unexpected 200 body"))
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
return {
|
|
2602
|
+
ok: false,
|
|
2603
|
+
error: mapBetterAuthError("verifyOtp", body)
|
|
2604
|
+
};
|
|
2605
|
+
} catch (err) {
|
|
2606
|
+
return {
|
|
2607
|
+
ok: false,
|
|
2608
|
+
error: mapFetchError$1("verifyOtp", err, options?.signal)
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async getSession(options) {
|
|
2613
|
+
if (options?.signal?.aborted) return {
|
|
2614
|
+
ok: false,
|
|
2615
|
+
error: Errors.cancelled({ operation: "getSession" })
|
|
2616
|
+
};
|
|
2617
|
+
try {
|
|
2618
|
+
const res = await this.fetchImpl(this.url("/api/auth/get-session"), withSignal$1({
|
|
2619
|
+
method: "GET",
|
|
2620
|
+
credentials: "include"
|
|
2621
|
+
}, options?.signal));
|
|
2622
|
+
if (options?.signal?.aborted) return {
|
|
2623
|
+
ok: false,
|
|
2624
|
+
error: Errors.cancelled({ operation: "getSession" })
|
|
2625
|
+
};
|
|
2626
|
+
if (!res.ok) return {
|
|
2627
|
+
ok: false,
|
|
2628
|
+
error: Errors.providerError("better-auth", "getSession", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
2629
|
+
};
|
|
2630
|
+
const body = await safeJson$1(res);
|
|
2631
|
+
if (body === null) return {
|
|
2632
|
+
ok: true,
|
|
2633
|
+
value: null
|
|
2634
|
+
};
|
|
2635
|
+
if (typeof body === "object" && body !== null) {
|
|
2636
|
+
const okBody = body;
|
|
2637
|
+
if (typeof okBody.user === "object" && okBody.user !== null) return {
|
|
2638
|
+
ok: true,
|
|
2639
|
+
value: authSessionFromBetterAuth$1(okBody.session?.token ?? okBody.session?.id ?? "session", okBody.user)
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
return {
|
|
2643
|
+
ok: true,
|
|
2644
|
+
value: null
|
|
2645
|
+
};
|
|
2646
|
+
} catch (err) {
|
|
2647
|
+
return {
|
|
2648
|
+
ok: false,
|
|
2649
|
+
error: mapFetchError$1("getSession", err, options?.signal)
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
async signOut(options) {
|
|
2654
|
+
if (options?.signal?.aborted) return {
|
|
2655
|
+
ok: false,
|
|
2656
|
+
error: Errors.cancelled({ operation: "signOut" })
|
|
2657
|
+
};
|
|
2658
|
+
try {
|
|
2659
|
+
const res = await this.fetchImpl(this.url("/api/auth/sign-out"), withSignal$1({
|
|
2660
|
+
method: "POST",
|
|
2661
|
+
headers: { "Content-Type": "application/json" },
|
|
2662
|
+
body: "{}",
|
|
2663
|
+
credentials: "include"
|
|
2664
|
+
}, options?.signal));
|
|
2665
|
+
if (options?.signal?.aborted) return {
|
|
2666
|
+
ok: false,
|
|
2667
|
+
error: Errors.cancelled({ operation: "signOut" })
|
|
2668
|
+
};
|
|
2669
|
+
if (res.ok) return {
|
|
2670
|
+
ok: true,
|
|
2671
|
+
value: void 0
|
|
2672
|
+
};
|
|
2673
|
+
return {
|
|
2674
|
+
ok: false,
|
|
2675
|
+
error: Errors.providerError("better-auth", "signOut", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
2676
|
+
};
|
|
2677
|
+
} catch (err) {
|
|
2678
|
+
return {
|
|
2679
|
+
ok: false,
|
|
2680
|
+
error: mapFetchError$1("signOut", err, options?.signal)
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
async getConvexJwt(options) {
|
|
2685
|
+
if (options?.signal?.aborted) return {
|
|
2686
|
+
ok: false,
|
|
2687
|
+
error: Errors.cancelled({ operation: "getConvexJwt" })
|
|
2688
|
+
};
|
|
2689
|
+
try {
|
|
2690
|
+
const res = await this.fetchImpl(this.url("/api/auth/convex/token"), withSignal$1({
|
|
2691
|
+
method: "GET",
|
|
2692
|
+
credentials: "include"
|
|
2693
|
+
}, options?.signal));
|
|
2694
|
+
if (options?.signal?.aborted) return {
|
|
2695
|
+
ok: false,
|
|
2696
|
+
error: Errors.cancelled({ operation: "getConvexJwt" })
|
|
2697
|
+
};
|
|
2698
|
+
if (res.status === 401) return {
|
|
2699
|
+
ok: false,
|
|
2700
|
+
error: Errors.notAuthenticated()
|
|
2701
|
+
};
|
|
2702
|
+
if (!res.ok) return {
|
|
2703
|
+
ok: false,
|
|
2704
|
+
error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
2705
|
+
};
|
|
2706
|
+
const body = await safeJson$1(res);
|
|
2707
|
+
if (typeof body === "object" && body !== null) {
|
|
2708
|
+
const okBody = body;
|
|
2709
|
+
if (typeof okBody.token === "string" && okBody.token.length > 0) return {
|
|
2710
|
+
ok: true,
|
|
2711
|
+
value: {
|
|
2712
|
+
token: toJwtToken(okBody.token),
|
|
2713
|
+
expEpochSeconds: toEpochSeconds(decodeJwtExp$1(okBody.token))
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
return {
|
|
2718
|
+
ok: false,
|
|
2719
|
+
error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error("unexpected body"))
|
|
2720
|
+
};
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
return {
|
|
2723
|
+
ok: false,
|
|
2724
|
+
error: mapFetchError$1("getConvexJwt", err, options?.signal)
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
function BetterAuthBrowserLayer(deps) {
|
|
2730
|
+
return Layer.succeed(AuthClientPortTag, authClientPortFromPromiseAdapter(new BetterAuthBrowserAdapter(deps)));
|
|
2731
|
+
}
|
|
2732
|
+
//#endregion
|
|
2733
|
+
//#region src/adapters/auth-client/cookie-jar.ts
|
|
2734
|
+
function parseSetCookie(raw) {
|
|
2735
|
+
const parts = raw.split(";").map((p) => p.trim());
|
|
2736
|
+
if (parts.length === 0 || parts[0] === void 0) return null;
|
|
2737
|
+
const nameValue = parts[0];
|
|
2738
|
+
const eq = nameValue.indexOf("=");
|
|
2739
|
+
if (eq < 0) return null;
|
|
2740
|
+
const name = nameValue.slice(0, eq).trim();
|
|
2741
|
+
const value = nameValue.slice(eq + 1).trim();
|
|
2742
|
+
if (name.length === 0) return null;
|
|
2743
|
+
let maxAgeSeconds = null;
|
|
2744
|
+
let expiresEpochMs = null;
|
|
2745
|
+
let path = null;
|
|
2746
|
+
let httpOnly = false;
|
|
2747
|
+
let secure = false;
|
|
2748
|
+
let sameSite = null;
|
|
2749
|
+
for (let i = 1; i < parts.length; i++) {
|
|
2750
|
+
const part = parts[i];
|
|
2751
|
+
if (part === void 0) continue;
|
|
2752
|
+
const partEq = part.indexOf("=");
|
|
2753
|
+
const key = (partEq < 0 ? part : part.slice(0, partEq)).trim().toLowerCase();
|
|
2754
|
+
const val = partEq < 0 ? "" : part.slice(partEq + 1).trim();
|
|
2755
|
+
if (key === "max-age") {
|
|
2756
|
+
const n = Number(val);
|
|
2757
|
+
if (Number.isFinite(n)) maxAgeSeconds = n;
|
|
2758
|
+
} else if (key === "expires") {
|
|
2759
|
+
const t = Date.parse(val);
|
|
2760
|
+
if (Number.isFinite(t)) expiresEpochMs = t;
|
|
2761
|
+
} else if (key === "path") path = val;
|
|
2762
|
+
else if (key === "httponly") httpOnly = true;
|
|
2763
|
+
else if (key === "secure") secure = true;
|
|
2764
|
+
else if (key === "samesite") {
|
|
2765
|
+
const lc = val.toLowerCase();
|
|
2766
|
+
if (lc === "strict" || lc === "lax" || lc === "none") sameSite = lc;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
name,
|
|
2771
|
+
value,
|
|
2772
|
+
maxAgeSeconds,
|
|
2773
|
+
expiresEpochMs,
|
|
2774
|
+
path,
|
|
2775
|
+
httpOnly,
|
|
2776
|
+
secure,
|
|
2777
|
+
sameSite
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function isExpired(cookie, nowEpochMs) {
|
|
2781
|
+
if (cookie.maxAgeSeconds !== null) {
|
|
2782
|
+
if (cookie.maxAgeSeconds <= 0) return true;
|
|
2783
|
+
return nowEpochMs >= cookie.storedAtEpochMs + cookie.maxAgeSeconds * 1e3;
|
|
2784
|
+
}
|
|
2785
|
+
if (cookie.expiresEpochMs !== null) return nowEpochMs >= cookie.expiresEpochMs;
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
var CookieJar = class {
|
|
2789
|
+
store = /* @__PURE__ */ new Map();
|
|
2790
|
+
set(host, setCookieHeaders) {
|
|
2791
|
+
let perHost = this.store.get(host);
|
|
2792
|
+
const now = Date.now();
|
|
2793
|
+
for (const raw of setCookieHeaders) {
|
|
2794
|
+
const parsed = parseSetCookie(raw);
|
|
2795
|
+
if (parsed === null) continue;
|
|
2796
|
+
if (perHost === void 0) {
|
|
2797
|
+
perHost = /* @__PURE__ */ new Map();
|
|
2798
|
+
this.store.set(host, perHost);
|
|
2799
|
+
}
|
|
2800
|
+
if (parsed.maxAgeSeconds !== null && parsed.maxAgeSeconds <= 0) {
|
|
2801
|
+
perHost.delete(parsed.name);
|
|
2802
|
+
continue;
|
|
2803
|
+
}
|
|
2804
|
+
perHost.set(parsed.name, {
|
|
2805
|
+
...parsed,
|
|
2806
|
+
storedAtEpochMs: now
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
if (perHost !== void 0 && perHost.size === 0) this.store.delete(host);
|
|
2810
|
+
}
|
|
2811
|
+
getCookieHeader(host) {
|
|
2812
|
+
const perHost = this.store.get(host);
|
|
2813
|
+
if (perHost === void 0 || perHost.size === 0) return null;
|
|
2814
|
+
const now = Date.now();
|
|
2815
|
+
const live = [];
|
|
2816
|
+
for (const [name, cookie] of perHost.entries()) {
|
|
2817
|
+
if (isExpired(cookie, now)) {
|
|
2818
|
+
perHost.delete(name);
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
live.push(`${name}=${cookie.value}`);
|
|
2822
|
+
}
|
|
2823
|
+
if (live.length === 0) {
|
|
2824
|
+
this.store.delete(host);
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
return live.join("; ");
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
//#endregion
|
|
2831
|
+
//#region src/adapters/auth-client/BetterAuthNodeAdapter.ts
|
|
2832
|
+
function withSignal(init, signal) {
|
|
2833
|
+
return signal === void 0 ? init : {
|
|
2834
|
+
...init,
|
|
2835
|
+
signal
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
const DEFAULT_JWT_LIFETIME_S = 900;
|
|
2839
|
+
function decodeJwtExp(jwt) {
|
|
2840
|
+
const parts = jwt.split(".");
|
|
2841
|
+
if (parts.length < 2 || parts[1] === void 0) return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S;
|
|
2842
|
+
try {
|
|
2843
|
+
const raw = parts[1].replace(/\s+/g, "");
|
|
2844
|
+
const pad = "=".repeat((4 - raw.length % 4) % 4);
|
|
2845
|
+
const decoded = Buffer.from(raw.replace(/-/g, "+").replace(/_/g, "/") + pad, "base64").toString("utf-8");
|
|
2846
|
+
const payload = JSON.parse(decoded);
|
|
2847
|
+
if (typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp > 0) return payload.exp;
|
|
2848
|
+
} catch {}
|
|
2849
|
+
return Math.floor(Date.now() / 1e3) + DEFAULT_JWT_LIFETIME_S;
|
|
2850
|
+
}
|
|
2851
|
+
function authSessionFromBetterAuth(token, user) {
|
|
2852
|
+
return {
|
|
2853
|
+
authUserId: toAuthUserId(user.id),
|
|
2854
|
+
email: toEmail(user.email),
|
|
2855
|
+
token: toSessionToken(token),
|
|
2856
|
+
expiresAt: toEpochMs(Date.now() + 10080 * 60 * 1e3)
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
async function safeJson(res) {
|
|
2860
|
+
const raw = await res.text();
|
|
2861
|
+
if (raw.length === 0 || raw === "null") return null;
|
|
2862
|
+
try {
|
|
2863
|
+
return JSON.parse(raw);
|
|
2864
|
+
} catch {
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
function hostFromBaseUrl(baseUrl) {
|
|
2869
|
+
try {
|
|
2870
|
+
return new URL(baseUrl).host;
|
|
2871
|
+
} catch {
|
|
2872
|
+
return baseUrl;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function originFromBaseUrl(baseUrl) {
|
|
2876
|
+
try {
|
|
2877
|
+
return new URL(baseUrl).origin;
|
|
2878
|
+
} catch {
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function setCookiesFromResponse(jar, host, res) {
|
|
2883
|
+
const ext = res.headers;
|
|
2884
|
+
let setCookies = [];
|
|
2885
|
+
if (typeof ext.getSetCookie === "function") setCookies = ext.getSetCookie();
|
|
2886
|
+
else res.headers.forEach((value, key) => {
|
|
2887
|
+
if (key.toLowerCase() === "set-cookie") setCookies.push(value);
|
|
2888
|
+
});
|
|
2889
|
+
if (setCookies.length > 0) jar.set(host, setCookies);
|
|
2890
|
+
}
|
|
2891
|
+
function isAbortError(err, signal) {
|
|
2892
|
+
return signal?.aborted === true || err instanceof Error && err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError";
|
|
2893
|
+
}
|
|
2894
|
+
function mapFetchError(operation, err, signal) {
|
|
2895
|
+
if (isAbortError(err, signal)) return Errors.cancelled({ operation });
|
|
2896
|
+
return Errors.networkError(operation, err instanceof Error ? err : new Error(String(err)));
|
|
2897
|
+
}
|
|
2898
|
+
var BetterAuthNodeAdapter = class {
|
|
2899
|
+
authBaseUrl;
|
|
2900
|
+
host;
|
|
2901
|
+
origin;
|
|
2902
|
+
cookieJar;
|
|
2903
|
+
fetchImpl;
|
|
2904
|
+
constructor(deps) {
|
|
2905
|
+
this.authBaseUrl = deps.authBaseUrl.replace(/\/$/, "");
|
|
2906
|
+
this.host = hostFromBaseUrl(this.authBaseUrl);
|
|
2907
|
+
this.origin = deps.origin?.replace(/\/$/, "");
|
|
2908
|
+
this.cookieJar = deps.cookieJar ?? new CookieJar();
|
|
2909
|
+
this.fetchImpl = deps.fetch ?? fetch;
|
|
2910
|
+
}
|
|
2911
|
+
url(path) {
|
|
2912
|
+
return resolveAuthClientUrl(this.authBaseUrl, path);
|
|
2913
|
+
}
|
|
2914
|
+
headersWithCookie(extra = {}) {
|
|
2915
|
+
const cookie = this.cookieJar.getCookieHeader(this.host);
|
|
2916
|
+
return cookie !== null ? {
|
|
2917
|
+
...extra,
|
|
2918
|
+
cookie
|
|
2919
|
+
} : { ...extra };
|
|
2920
|
+
}
|
|
2921
|
+
async canSendOtp(_input, options) {
|
|
2922
|
+
if (options?.signal?.aborted) return {
|
|
2923
|
+
ok: false,
|
|
2924
|
+
error: Errors.cancelled({ operation: "canSendOtp" })
|
|
2925
|
+
};
|
|
2926
|
+
return {
|
|
2927
|
+
ok: true,
|
|
2928
|
+
value: {
|
|
2929
|
+
allowed: true,
|
|
2930
|
+
cooldownMs: toDurationMs(0)
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
async sendOtp(input, options) {
|
|
2935
|
+
if (options?.signal?.aborted) return {
|
|
2936
|
+
ok: false,
|
|
2937
|
+
error: Errors.cancelled({ operation: "sendOtp" })
|
|
2938
|
+
};
|
|
2939
|
+
try {
|
|
2940
|
+
const res = await this.fetchImpl(this.url("/api/auth/email-otp/send-verification-otp"), withSignal({
|
|
2941
|
+
method: "POST",
|
|
2942
|
+
headers: {
|
|
2943
|
+
"content-type": "application/json",
|
|
2944
|
+
...this.origin ? { origin: this.origin } : {}
|
|
2945
|
+
},
|
|
2946
|
+
body: JSON.stringify({
|
|
2947
|
+
email: input.email,
|
|
2948
|
+
type: "sign-in"
|
|
2949
|
+
})
|
|
2950
|
+
}, options?.signal));
|
|
2951
|
+
if (options?.signal?.aborted) return {
|
|
2952
|
+
ok: false,
|
|
2953
|
+
error: Errors.cancelled({ operation: "sendOtp" })
|
|
2954
|
+
};
|
|
2955
|
+
if (res.ok) return {
|
|
2956
|
+
ok: true,
|
|
2957
|
+
value: void 0
|
|
2958
|
+
};
|
|
2959
|
+
if (res.status === 429) return {
|
|
2960
|
+
ok: false,
|
|
2961
|
+
error: Errors.rateLimited({ resource: "better-auth/sendOtp" })
|
|
2962
|
+
};
|
|
2963
|
+
const body = await safeJson(res);
|
|
2964
|
+
if (typeof body === "object" && body !== null) {
|
|
2965
|
+
const errBody = body;
|
|
2966
|
+
if (errBody.code === "INVALID_EMAIL" || errBody.code === "VALIDATION_ERROR") return {
|
|
2967
|
+
ok: false,
|
|
2968
|
+
error: Errors.invalidInput("email", errBody.message ?? "invalid email")
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
return {
|
|
2972
|
+
ok: false,
|
|
2973
|
+
error: Errors.providerError("better-auth", "sendOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
2974
|
+
};
|
|
2975
|
+
} catch (err) {
|
|
2976
|
+
return {
|
|
2977
|
+
ok: false,
|
|
2978
|
+
error: mapFetchError("sendOtp", err, options?.signal)
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
async verifyOtp(input, options) {
|
|
2983
|
+
if (options?.signal?.aborted) return {
|
|
2984
|
+
ok: false,
|
|
2985
|
+
error: Errors.cancelled({ operation: "verifyOtp" })
|
|
2986
|
+
};
|
|
2987
|
+
try {
|
|
2988
|
+
const res = await this.fetchImpl(this.url("/api/auth/sign-in/email-otp"), withSignal({
|
|
2989
|
+
method: "POST",
|
|
2990
|
+
headers: {
|
|
2991
|
+
"content-type": "application/json",
|
|
2992
|
+
...this.origin ? { origin: this.origin } : {}
|
|
2993
|
+
},
|
|
2994
|
+
body: JSON.stringify({
|
|
2995
|
+
email: input.email,
|
|
2996
|
+
otp: input.otp
|
|
2997
|
+
})
|
|
2998
|
+
}, options?.signal));
|
|
2999
|
+
if (options?.signal?.aborted) return {
|
|
3000
|
+
ok: false,
|
|
3001
|
+
error: Errors.cancelled({ operation: "verifyOtp" })
|
|
3002
|
+
};
|
|
3003
|
+
setCookiesFromResponse(this.cookieJar, this.host, res);
|
|
3004
|
+
const body = await safeJson(res);
|
|
3005
|
+
if (res.ok && typeof body === "object" && body !== null) {
|
|
3006
|
+
const okBody = body;
|
|
3007
|
+
if (typeof okBody.token === "string" && typeof okBody.user === "object" && okBody.user !== null) return {
|
|
3008
|
+
ok: true,
|
|
3009
|
+
value: authSessionFromBetterAuth(okBody.token, okBody.user)
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
if (!res.ok) {
|
|
3013
|
+
if (typeof body === "object" && body !== null) {
|
|
3014
|
+
const errBody = body;
|
|
3015
|
+
if (errBody.code === "OTP_EXPIRED") return {
|
|
3016
|
+
ok: false,
|
|
3017
|
+
error: Errors.otpExpired()
|
|
3018
|
+
};
|
|
3019
|
+
if (errBody.code === "INVALID_OTP") return {
|
|
3020
|
+
ok: false,
|
|
3021
|
+
error: Errors.invalidInput("otp", errBody.message ?? "invalid OTP")
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return {
|
|
3026
|
+
ok: false,
|
|
3027
|
+
error: Errors.providerError("better-auth", "verifyOtp", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
3028
|
+
};
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
return {
|
|
3031
|
+
ok: false,
|
|
3032
|
+
error: mapFetchError("verifyOtp", err, options?.signal)
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
async getSession(options) {
|
|
3037
|
+
if (options?.signal?.aborted) return {
|
|
3038
|
+
ok: false,
|
|
3039
|
+
error: Errors.cancelled({ operation: "getSession" })
|
|
3040
|
+
};
|
|
3041
|
+
try {
|
|
3042
|
+
const res = await this.fetchImpl(this.url("/api/auth/get-session"), withSignal({
|
|
3043
|
+
method: "GET",
|
|
3044
|
+
headers: this.headersWithCookie()
|
|
3045
|
+
}, options?.signal));
|
|
3046
|
+
if (options?.signal?.aborted) return {
|
|
3047
|
+
ok: false,
|
|
3048
|
+
error: Errors.cancelled({ operation: "getSession" })
|
|
3049
|
+
};
|
|
3050
|
+
if (!res.ok) return {
|
|
3051
|
+
ok: false,
|
|
3052
|
+
error: Errors.providerError("better-auth", "getSession", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
3053
|
+
};
|
|
3054
|
+
const body = await safeJson(res);
|
|
3055
|
+
if (body === null) return {
|
|
3056
|
+
ok: true,
|
|
3057
|
+
value: null
|
|
3058
|
+
};
|
|
3059
|
+
if (typeof body === "object" && body !== null) {
|
|
3060
|
+
const okBody = body;
|
|
3061
|
+
if (typeof okBody.user === "object" && okBody.user !== null) return {
|
|
3062
|
+
ok: true,
|
|
3063
|
+
value: authSessionFromBetterAuth(okBody.session?.token ?? okBody.session?.id ?? "session", okBody.user)
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
return {
|
|
3067
|
+
ok: true,
|
|
3068
|
+
value: null
|
|
3069
|
+
};
|
|
3070
|
+
} catch (err) {
|
|
3071
|
+
return {
|
|
3072
|
+
ok: false,
|
|
3073
|
+
error: mapFetchError("getSession", err, options?.signal)
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
async signOut(options) {
|
|
3078
|
+
if (options?.signal?.aborted) return {
|
|
3079
|
+
ok: false,
|
|
3080
|
+
error: Errors.cancelled({ operation: "signOut" })
|
|
3081
|
+
};
|
|
3082
|
+
try {
|
|
3083
|
+
const signOutOrigin = originFromBaseUrl(this.authBaseUrl) ?? this.origin;
|
|
3084
|
+
const res = await this.fetchImpl(this.url("/api/auth/sign-out"), withSignal({
|
|
3085
|
+
method: "POST",
|
|
3086
|
+
headers: this.headersWithCookie({
|
|
3087
|
+
"content-type": "application/json",
|
|
3088
|
+
...signOutOrigin ? { origin: signOutOrigin } : {}
|
|
3089
|
+
}),
|
|
3090
|
+
body: "{}"
|
|
3091
|
+
}, options?.signal));
|
|
3092
|
+
if (options?.signal?.aborted) return {
|
|
3093
|
+
ok: false,
|
|
3094
|
+
error: Errors.cancelled({ operation: "signOut" })
|
|
3095
|
+
};
|
|
3096
|
+
setCookiesFromResponse(this.cookieJar, this.host, res);
|
|
3097
|
+
if (res.ok) return {
|
|
3098
|
+
ok: true,
|
|
3099
|
+
value: void 0
|
|
3100
|
+
};
|
|
3101
|
+
return {
|
|
3102
|
+
ok: false,
|
|
3103
|
+
error: Errors.providerError("better-auth", "signOut", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
3104
|
+
};
|
|
3105
|
+
} catch (err) {
|
|
3106
|
+
return {
|
|
3107
|
+
ok: false,
|
|
3108
|
+
error: mapFetchError("signOut", err, options?.signal)
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
async getConvexJwt(options) {
|
|
3113
|
+
if (options?.signal?.aborted) return {
|
|
3114
|
+
ok: false,
|
|
3115
|
+
error: Errors.cancelled({ operation: "getConvexJwt" })
|
|
3116
|
+
};
|
|
3117
|
+
try {
|
|
3118
|
+
const res = await this.fetchImpl(this.url("/api/auth/convex/token"), withSignal({
|
|
3119
|
+
method: "GET",
|
|
3120
|
+
headers: this.headersWithCookie()
|
|
3121
|
+
}, options?.signal));
|
|
3122
|
+
if (options?.signal?.aborted) return {
|
|
3123
|
+
ok: false,
|
|
3124
|
+
error: Errors.cancelled({ operation: "getConvexJwt" })
|
|
3125
|
+
};
|
|
3126
|
+
if (res.status === 401) return {
|
|
3127
|
+
ok: false,
|
|
3128
|
+
error: Errors.notAuthenticated()
|
|
3129
|
+
};
|
|
3130
|
+
if (!res.ok) return {
|
|
3131
|
+
ok: false,
|
|
3132
|
+
error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error(`HTTP ${res.status}`))
|
|
3133
|
+
};
|
|
3134
|
+
const body = await safeJson(res);
|
|
3135
|
+
if (typeof body === "object" && body !== null) {
|
|
3136
|
+
const okBody = body;
|
|
3137
|
+
if (typeof okBody.token === "string" && okBody.token.length > 0) return {
|
|
3138
|
+
ok: true,
|
|
3139
|
+
value: {
|
|
3140
|
+
token: toJwtToken(okBody.token),
|
|
3141
|
+
expEpochSeconds: toEpochSeconds(decodeJwtExp(okBody.token))
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
return {
|
|
3146
|
+
ok: false,
|
|
3147
|
+
error: Errors.providerError("better-auth", "getConvexJwt", /* @__PURE__ */ new Error("unexpected body"))
|
|
3148
|
+
};
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
return {
|
|
3151
|
+
ok: false,
|
|
3152
|
+
error: mapFetchError("getConvexJwt", err, options?.signal)
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
function BetterAuthNodeLayer(deps) {
|
|
3158
|
+
return Layer.succeed(AuthClientPortTag, authClientPortFromPromiseAdapter(new BetterAuthNodeAdapter(deps)));
|
|
3159
|
+
}
|
|
3160
|
+
//#endregion
|
|
3161
|
+
//#region src/ports/bootstrap.ts
|
|
3162
|
+
var BootstrapError = class extends Data.TaggedError("BootstrapError") {};
|
|
3163
|
+
function bootstrapErrorFromCapxul(kind, error) {
|
|
3164
|
+
return new BootstrapError({
|
|
3165
|
+
operation: "resolve",
|
|
3166
|
+
kind,
|
|
3167
|
+
publicCode: error.code,
|
|
3168
|
+
cause: error,
|
|
3169
|
+
...kind === "notAuthenticated" || error.details === void 0 ? {} : { details: error.details }
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
var BootstrapPortTag = class extends Context.Tag("@capxul/sdk/ports/BootstrapPort")() {};
|
|
3173
|
+
//#endregion
|
|
3174
|
+
//#region src/adapters/bootstrap/HttpBootstrapAdapter.ts
|
|
3175
|
+
async function safeText(res) {
|
|
3176
|
+
try {
|
|
3177
|
+
return await res.text();
|
|
3178
|
+
} catch {
|
|
3179
|
+
return "";
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
var HttpBootstrapAdapter = class {
|
|
3183
|
+
bootstrapBaseUrl;
|
|
3184
|
+
fetchImpl;
|
|
3185
|
+
constructor(deps) {
|
|
3186
|
+
this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
|
|
3187
|
+
this.fetchImpl = deps.fetch ?? fetch;
|
|
3188
|
+
}
|
|
3189
|
+
resolve(input) {
|
|
3190
|
+
return Effect.tryPromise({
|
|
3191
|
+
try: () => {
|
|
3192
|
+
const headers = {
|
|
3193
|
+
"content-type": "application/json",
|
|
3194
|
+
...input.origin === void 0 ? {} : { origin: input.origin }
|
|
3195
|
+
};
|
|
3196
|
+
return this.fetchImpl(`${this.bootstrapBaseUrl}/v1/client/bootstrap`, {
|
|
3197
|
+
method: "POST",
|
|
3198
|
+
headers,
|
|
3199
|
+
body: JSON.stringify({ publishableKey: input.publishableKey })
|
|
3200
|
+
});
|
|
3201
|
+
},
|
|
3202
|
+
catch: (cause) => bootstrapErrorFromCapxul("network", Errors.networkError("bootstrap", cause))
|
|
3203
|
+
}).pipe(Effect.flatMap((res) => this.mapResponse(res)));
|
|
3204
|
+
}
|
|
3205
|
+
mapResponse(res) {
|
|
3206
|
+
if (res.ok) return Effect.tryPromise({
|
|
3207
|
+
try: async () => {
|
|
3208
|
+
const body = await res.json();
|
|
3209
|
+
const decoded = Schema.decodeUnknownEither(BootstrapEnvelope)(body);
|
|
3210
|
+
if (Either.isLeft(decoded)) throw Errors.invalidInput("bootstrapEnvelope", decoded.left.message);
|
|
3211
|
+
const { state } = decoded.right;
|
|
3212
|
+
return {
|
|
3213
|
+
applicationId: state.applicationId,
|
|
3214
|
+
chainId: state.chainId,
|
|
3215
|
+
sessionToken: state.sessionToken,
|
|
3216
|
+
issuedAt: state.issuedAt,
|
|
3217
|
+
expiresIn: state.expiresIn,
|
|
3218
|
+
authBaseUrl: normalizeRuntimeUrl("authBaseUrl", state.authBaseUrl),
|
|
3219
|
+
convexUrl: normalizeRuntimeUrl("convexUrl", state.convexUrl)
|
|
3220
|
+
};
|
|
3221
|
+
},
|
|
3222
|
+
catch: (cause) => {
|
|
3223
|
+
if (cause instanceof CapxulError && cause.code === "INVALID_INPUT") return bootstrapErrorFromCapxul("invalidInput", cause);
|
|
3224
|
+
return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
return Effect.promise(() => safeText(res)).pipe(Effect.flatMap((body) => {
|
|
3228
|
+
if (res.status === 401 || body.startsWith("NOT_AUTHENTICATED")) return Effect.fail(bootstrapErrorFromCapxul("notAuthenticated", Errors.notAuthenticated()));
|
|
3229
|
+
if (res.status === 400 || body.startsWith("INVALID_INPUT")) return Effect.fail(bootstrapErrorFromCapxul("invalidInput", Errors.invalidInput("publishableKey", "rejected by bootstrap")));
|
|
3230
|
+
return Effect.fail(bootstrapErrorFromCapxul("provider", Errors.providerError("convex", "bootstrap", /* @__PURE__ */ new Error(`HTTP ${res.status}`))));
|
|
3231
|
+
}));
|
|
3232
|
+
}
|
|
3233
|
+
};
|
|
3234
|
+
function HttpBootstrapLayer(deps) {
|
|
3235
|
+
return Layer.succeed(BootstrapPortTag, new HttpBootstrapAdapter(deps));
|
|
3236
|
+
}
|
|
3237
|
+
function normalizeRuntimeUrl(field, raw) {
|
|
3238
|
+
let parsed;
|
|
3239
|
+
try {
|
|
3240
|
+
parsed = new URL(raw);
|
|
3241
|
+
} catch {
|
|
3242
|
+
throw Errors.invalidInput(field, "must be an http or https URL");
|
|
3243
|
+
}
|
|
3244
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
|
|
3245
|
+
return parsed.toString().replace(/\/$/, "");
|
|
3246
|
+
}
|
|
3247
|
+
//#endregion
|
|
3248
|
+
//#region src/adapters/clock/SystemClockAdapter.ts
|
|
3249
|
+
var SystemClockAdapter = class {
|
|
3250
|
+
now = Effect.try({
|
|
3251
|
+
try: () => toEpochMs(Date.now()),
|
|
3252
|
+
catch: (cause) => new ClockError({
|
|
3253
|
+
operation: "now",
|
|
3254
|
+
cause
|
|
3255
|
+
})
|
|
3256
|
+
});
|
|
3257
|
+
sleep(duration) {
|
|
3258
|
+
return Effect.async((resume) => {
|
|
3259
|
+
const timeout = setTimeout(() => resume(Effect.void), duration);
|
|
3260
|
+
return Effect.sync(() => clearTimeout(timeout));
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
function SystemClockLayer() {
|
|
3265
|
+
return Layer.succeed(ClockPortTag, new SystemClockAdapter());
|
|
3266
|
+
}
|
|
3267
|
+
//#endregion
|
|
3268
|
+
//#region src/ports/convex-call.ts
|
|
3269
|
+
var ConvexCallError = class extends Data.TaggedError("ConvexCallError") {};
|
|
3270
|
+
function convexCallErrorFromCapxul(operation, error) {
|
|
3271
|
+
return new ConvexCallError({
|
|
3272
|
+
operation,
|
|
3273
|
+
publicCode: error.code,
|
|
3274
|
+
publicError: error,
|
|
3275
|
+
cause: error,
|
|
3276
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
var ConvexCallPortTag = class extends Context.Tag("@capxul/sdk/ports/ConvexCallPort")() {};
|
|
3280
|
+
//#endregion
|
|
3281
|
+
//#region src/adapters/convex-call/ConvexCallAdapter.ts
|
|
3282
|
+
var ConvexCallAdapter = class {
|
|
3283
|
+
#client;
|
|
3284
|
+
#tokenProvider;
|
|
3285
|
+
constructor(deps) {
|
|
3286
|
+
this.#client = deps.client ?? new ConvexClient(deps.convexUrl);
|
|
3287
|
+
this.#tokenProvider = deps.tokenProvider;
|
|
3288
|
+
if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
|
|
3289
|
+
}
|
|
3290
|
+
refreshAuth() {
|
|
3291
|
+
if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
|
|
3292
|
+
}
|
|
3293
|
+
query(fn, args) {
|
|
3294
|
+
return Effect.tryPromise({
|
|
3295
|
+
try: () => this.#client.query(fn, args),
|
|
3296
|
+
catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
mutation(fn, args) {
|
|
3300
|
+
return Effect.tryPromise({
|
|
3301
|
+
try: () => this.#client.mutation(fn, args),
|
|
3302
|
+
catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
action(fn, args) {
|
|
3306
|
+
return Effect.tryPromise({
|
|
3307
|
+
try: () => this.#client.action(fn, args),
|
|
3308
|
+
catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
subscribe(fn, args, callback) {
|
|
3312
|
+
return Effect.try({
|
|
3313
|
+
try: () => {
|
|
3314
|
+
const path = getFunctionName(fn);
|
|
3315
|
+
const unsubscribe = this.#client.onUpdate(fn, args, (value) => callback({
|
|
3316
|
+
status: "ok",
|
|
3317
|
+
value
|
|
3318
|
+
}), (err) => callback({
|
|
3319
|
+
status: "error",
|
|
3320
|
+
error: mapToCapxulError(path, err)
|
|
3321
|
+
}));
|
|
3322
|
+
let active = true;
|
|
3323
|
+
return () => {
|
|
3324
|
+
if (!active) return;
|
|
3325
|
+
active = false;
|
|
3326
|
+
unsubscribe();
|
|
3327
|
+
};
|
|
3328
|
+
},
|
|
3329
|
+
catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
|
|
3330
|
+
}).pipe(Effect.tap(() => Effect.sync(() => {
|
|
3331
|
+
callback({ status: "loading" });
|
|
3332
|
+
})));
|
|
3333
|
+
}
|
|
3334
|
+
async close() {
|
|
3335
|
+
await this.#client.close();
|
|
3336
|
+
}
|
|
3337
|
+
};
|
|
3338
|
+
function ConvexCallLayer(deps) {
|
|
3339
|
+
return Layer.scoped(ConvexCallPortTag, Effect.acquireRelease(Effect.sync(() => new ConvexCallAdapter(deps)), (adapter) => Effect.promise(() => adapter.close()).pipe(Effect.orDie)));
|
|
3340
|
+
}
|
|
3341
|
+
function mapToCapxulError(operation, err) {
|
|
3342
|
+
const decoded = decodeConvexError(err);
|
|
3343
|
+
if (decoded !== null) return decoded;
|
|
3344
|
+
if (err instanceof CapxulError) return err;
|
|
3345
|
+
if (err instanceof Error) {
|
|
3346
|
+
if (isTransportError(err)) return Errors.networkError(operation, err);
|
|
3347
|
+
return Errors.providerError("convex", operation, err);
|
|
3348
|
+
}
|
|
3349
|
+
return Errors.providerError("convex", operation, new Error(String(err)));
|
|
3350
|
+
}
|
|
3351
|
+
function mapToConvexCallError(operation, err) {
|
|
3352
|
+
return convexCallErrorFromCapxul(operation, mapToCapxulError(operation, err));
|
|
3353
|
+
}
|
|
3354
|
+
function isTransportError(err) {
|
|
3355
|
+
const message = err.message.toLowerCase();
|
|
3356
|
+
return message.includes("failed to fetch") || message.includes("network") || message.includes("econnrefused") || message.includes("econnreset") || message.includes("enotfound") || message.includes("etimedout") || message.includes("socket");
|
|
3357
|
+
}
|
|
3358
|
+
//#endregion
|
|
3359
|
+
//#region src/adapters/_shared/wire.ts
|
|
3360
|
+
/**
|
|
3361
|
+
* Documented brand-erasure helpers for the wire boundary.
|
|
3362
|
+
*
|
|
3363
|
+
* Branded primitives (`Address`, `ChainId`) carry compile-time tags that
|
|
3364
|
+
* vanish at runtime. When sending values across a wire — Convex function
|
|
3365
|
+
* args, viem's template-literal-typed parameters, raw JSON — we have to
|
|
3366
|
+
* remove the brand at the type level so the wire-side type system accepts
|
|
3367
|
+
* the value.
|
|
3368
|
+
*
|
|
3369
|
+
* Using these helpers (rather than ad-hoc `as number` / `as \`0x${string}\``
|
|
3370
|
+
* casts) localizes every de-branding site. If we ever need to centralize
|
|
3371
|
+
* validation (e.g. "must not de-brand a placeholder address"), the change
|
|
3372
|
+
* lands here once instead of grep-and-replace across every adapter.
|
|
3373
|
+
*/
|
|
3374
|
+
/**
|
|
3375
|
+
* De-brand a `ChainId` for the wire. The runtime representation is already
|
|
3376
|
+
* `number` — the cast removes the compile-time brand only.
|
|
3377
|
+
*/
|
|
3378
|
+
function wireChainId(chainId) {
|
|
3379
|
+
return chainId;
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* De-brand a `DurationMs` for the wire. The runtime representation is
|
|
3383
|
+
* already `number` — the cast removes the compile-time brand only.
|
|
3384
|
+
*
|
|
3385
|
+
* Used at Convex mutation call sites where the backend's schema expects
|
|
3386
|
+
* a plain `number` for `graceMs` (and similar duration fields). The
|
|
3387
|
+
* branded `DurationMs` type is the SDK's discipline; the wire format is
|
|
3388
|
+
* agnostic.
|
|
3389
|
+
*/
|
|
3390
|
+
function wireDurationMs(duration) {
|
|
3391
|
+
return duration;
|
|
3392
|
+
}
|
|
3393
|
+
//#endregion
|
|
3394
|
+
//#region src/ports/credentials.ts
|
|
3395
|
+
var CredentialsError = class extends Data.TaggedError("CredentialsError") {};
|
|
3396
|
+
function credentialsErrorFromCapxul(operation, error, cause = error) {
|
|
3397
|
+
return new CredentialsError({
|
|
3398
|
+
operation,
|
|
3399
|
+
publicCode: error.code,
|
|
3400
|
+
publicError: error,
|
|
3401
|
+
cause,
|
|
3402
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
var CredentialsPortTag = class extends Context.Tag("@capxul/sdk/ports/CredentialsPort")() {};
|
|
3406
|
+
//#endregion
|
|
3407
|
+
//#region src/adapters/credentials/ConvexCredentialsAdapter.ts
|
|
3408
|
+
var ConvexCredentialsAdapter = class {
|
|
3409
|
+
#convex;
|
|
3410
|
+
constructor(deps) {
|
|
3411
|
+
this.#convex = deps.convex;
|
|
3412
|
+
}
|
|
3413
|
+
listApplications(authUserId) {
|
|
3414
|
+
return this.#convex.query(applicationsListFn, { authUserId: String(authUserId) }).pipe(Effect.mapError((error) => credentialsErrorFromCapxul("listApplications", error.publicError, error)), Effect.flatMap((rows) => Effect.try({
|
|
3415
|
+
try: () => rows.map(brandDeveloperApplication),
|
|
3416
|
+
catch: (cause) => credentialsErrorFromUnknown("listApplications", cause)
|
|
3417
|
+
})));
|
|
3418
|
+
}
|
|
3419
|
+
createApplication(input) {
|
|
3420
|
+
return this.#convex.mutation(applicationsCreateFn, {
|
|
3421
|
+
authUserId: String(input.authUserId),
|
|
3422
|
+
name: input.name,
|
|
3423
|
+
allowedOrigins: input.allowedOrigins.map(String)
|
|
3424
|
+
}).pipe(Effect.mapError((error) => credentialsErrorFromCapxul("createApplication", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3425
|
+
try: () => brandDeveloperApplication(row),
|
|
3426
|
+
catch: (cause) => credentialsErrorFromUnknown("createApplication", cause)
|
|
3427
|
+
})));
|
|
3428
|
+
}
|
|
3429
|
+
mintPublishableKey(input) {
|
|
3430
|
+
return this.#convex.mutation(publishableKeysMintFn, {
|
|
3431
|
+
applicationId: String(input.applicationId),
|
|
3432
|
+
authUserId: String(input.authUserId)
|
|
3433
|
+
}).pipe(Effect.mapError((error) => credentialsErrorFromCapxul("mintPublishableKey", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3434
|
+
try: () => brandPublishableKeyRecord(row),
|
|
3435
|
+
catch: (cause) => credentialsErrorFromUnknown("mintPublishableKey", cause)
|
|
3436
|
+
})));
|
|
3437
|
+
}
|
|
3438
|
+
rotatePublishableKey(input) {
|
|
3439
|
+
return this.#convex.mutation(publishableKeysRotateFn, {
|
|
3440
|
+
keyId: String(input.keyId),
|
|
3441
|
+
authUserId: String(input.authUserId),
|
|
3442
|
+
graceMs: wireDurationMs(input.graceMs)
|
|
3443
|
+
}).pipe(Effect.mapError((error) => credentialsErrorFromCapxul("rotatePublishableKey", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3444
|
+
try: () => ({
|
|
3445
|
+
active: brandPublishableKeyRecord(row.active),
|
|
3446
|
+
retired: brandPublishableKeyRecord(row.retired)
|
|
3447
|
+
}),
|
|
3448
|
+
catch: (cause) => credentialsErrorFromUnknown("rotatePublishableKey", cause)
|
|
3449
|
+
})));
|
|
3450
|
+
}
|
|
3451
|
+
revokePublishableKey(input) {
|
|
3452
|
+
return this.#convex.mutation(publishableKeysRevokeFn, {
|
|
3453
|
+
keyId: String(input.keyId),
|
|
3454
|
+
authUserId: String(input.authUserId)
|
|
3455
|
+
}).pipe(Effect.mapError((error) => credentialsErrorFromCapxul("revokePublishableKey", error.publicError, error)), Effect.asVoid);
|
|
3456
|
+
}
|
|
3457
|
+
};
|
|
3458
|
+
function ConvexCredentialsLayer() {
|
|
3459
|
+
return Layer.effect(CredentialsPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexCredentialsAdapter({ convex })));
|
|
3460
|
+
}
|
|
3461
|
+
const applicationsListFn = makeFunctionReference("credentials/applications:list");
|
|
3462
|
+
const applicationsCreateFn = makeFunctionReference("credentials/applications:create");
|
|
3463
|
+
const publishableKeysMintFn = makeFunctionReference("credentials/publishableKeys:mint");
|
|
3464
|
+
const publishableKeysRotateFn = makeFunctionReference("credentials/publishableKeys:rotate");
|
|
3465
|
+
const publishableKeysRevokeFn = makeFunctionReference("credentials/publishableKeys:revoke");
|
|
3466
|
+
function brandDeveloperApplication(row) {
|
|
3467
|
+
return {
|
|
3468
|
+
id: toAppId(row.id),
|
|
3469
|
+
authUserId: toAuthUserId(row.authUserId),
|
|
3470
|
+
name: row.name,
|
|
3471
|
+
allowedOrigins: row.allowedOrigins.map((o) => toAllowedOrigin(o)),
|
|
3472
|
+
createdAt: toEpochMs(row.createdAt),
|
|
3473
|
+
archivedAt: row.archivedAt === null ? null : toEpochMs(row.archivedAt)
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
function brandPublishableKeyRecord(row) {
|
|
3477
|
+
return {
|
|
3478
|
+
id: toPublishableKeyId(row.id),
|
|
3479
|
+
applicationId: toAppId(row.applicationId),
|
|
3480
|
+
activeFromMs: toEpochMs(row.activeFromMs),
|
|
3481
|
+
gracePeriodEndsMs: row.gracePeriodEndsMs === null ? null : toEpochMs(row.gracePeriodEndsMs),
|
|
3482
|
+
revokedAt: row.revokedAt === null ? null : toEpochMs(row.revokedAt)
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
function credentialsErrorFromUnknown(operation, cause) {
|
|
3486
|
+
if (cause instanceof CapxulError) return credentialsErrorFromCapxul(operation, cause);
|
|
3487
|
+
return credentialsErrorFromCapxul(operation, Errors.providerError("credentials", operation, cause instanceof Error ? cause : new Error(String(cause))), cause);
|
|
3488
|
+
}
|
|
3489
|
+
//#endregion
|
|
3490
|
+
//#region src/ports/env.ts
|
|
3491
|
+
var EnvError = class extends Data.TaggedError("EnvError") {};
|
|
3492
|
+
function envErrorFromCapxul(key, error, cause = error) {
|
|
3493
|
+
return new EnvError({
|
|
3494
|
+
publicError: error,
|
|
3495
|
+
publicCode: error.code,
|
|
3496
|
+
key,
|
|
3497
|
+
...error.details === void 0 ? {} : { details: error.details },
|
|
3498
|
+
cause
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
var EnvPortTag = class extends Context.Tag("@capxul/sdk/ports/EnvPort")() {};
|
|
3502
|
+
//#endregion
|
|
3503
|
+
//#region src/env/_internal.ts
|
|
3504
|
+
const envFields = {
|
|
3505
|
+
NEXT_PUBLIC_CONVEX_URL: {
|
|
3506
|
+
"runtime": "client",
|
|
3507
|
+
"kind": "url",
|
|
3508
|
+
"required": true,
|
|
3509
|
+
"example": "https://example.convex.cloud"
|
|
3510
|
+
},
|
|
3511
|
+
NEXT_PUBLIC_CONVEX_SITE_URL: {
|
|
3512
|
+
"runtime": "client",
|
|
3513
|
+
"kind": "url",
|
|
3514
|
+
"required": true,
|
|
3515
|
+
"example": "https://example.convex.site"
|
|
3516
|
+
},
|
|
3517
|
+
NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY: {
|
|
3518
|
+
"runtime": "client",
|
|
3519
|
+
"kind": "nonempty",
|
|
3520
|
+
"required": true,
|
|
3521
|
+
"example": "pk_test_openfort"
|
|
3522
|
+
},
|
|
3523
|
+
NEXT_PUBLIC_SHIELD_PUBLISHABLE_KEY: {
|
|
3524
|
+
"runtime": "client",
|
|
3525
|
+
"kind": "nonempty",
|
|
3526
|
+
"required": true,
|
|
3527
|
+
"example": "shield_pk_test"
|
|
3528
|
+
},
|
|
3529
|
+
NEXT_PUBLIC_FEE_SPONSORSHIP_ID: {
|
|
3530
|
+
"runtime": "client",
|
|
3531
|
+
"kind": "nonempty",
|
|
3532
|
+
"required": false,
|
|
3533
|
+
"example": "fee_sponsor"
|
|
3534
|
+
},
|
|
3535
|
+
NEXT_PUBLIC_POSTHOG_KEY: {
|
|
3536
|
+
"runtime": "client",
|
|
3537
|
+
"kind": "nonempty",
|
|
3538
|
+
"required": false,
|
|
3539
|
+
"example": "phc_test"
|
|
3540
|
+
},
|
|
3541
|
+
NEXT_PUBLIC_POSTHOG_HOST: {
|
|
3542
|
+
"runtime": "client",
|
|
3543
|
+
"kind": "url",
|
|
3544
|
+
"required": false,
|
|
3545
|
+
"example": "https://us.i.posthog.com"
|
|
3546
|
+
},
|
|
3547
|
+
NEXT_PUBLIC_SENTRY_DSN: {
|
|
3548
|
+
"runtime": "client",
|
|
3549
|
+
"kind": "url",
|
|
3550
|
+
"required": false,
|
|
3551
|
+
"example": "https://public@example.ingest.sentry.io/1"
|
|
3552
|
+
},
|
|
3553
|
+
SITE_URL: {
|
|
3554
|
+
"runtime": "server",
|
|
3555
|
+
"kind": "url",
|
|
3556
|
+
"required": true,
|
|
3557
|
+
"example": "https://app.capxul.test"
|
|
3558
|
+
},
|
|
3559
|
+
OPENFORT_SECRET_KEY: {
|
|
3560
|
+
"runtime": "server",
|
|
3561
|
+
"kind": "nonempty",
|
|
3562
|
+
"required": true,
|
|
3563
|
+
"example": "sk_test_openfort"
|
|
3564
|
+
},
|
|
3565
|
+
OPENFORT_PUBLISHABLE_KEY: {
|
|
3566
|
+
"runtime": "server",
|
|
3567
|
+
"kind": "nonempty",
|
|
3568
|
+
"required": true,
|
|
3569
|
+
"example": "pk_server_openfort"
|
|
3570
|
+
},
|
|
3571
|
+
OPENFORT_WALLET_SECRET: {
|
|
3572
|
+
"runtime": "server",
|
|
3573
|
+
"kind": "nonempty",
|
|
3574
|
+
"required": true,
|
|
3575
|
+
"example": "wallet_secret"
|
|
3576
|
+
},
|
|
3577
|
+
SHIELD_PUBLISHABLE_KEY: {
|
|
3578
|
+
"runtime": "server",
|
|
3579
|
+
"kind": "nonempty",
|
|
3580
|
+
"required": true,
|
|
3581
|
+
"example": "shield_pk_server"
|
|
3582
|
+
},
|
|
3583
|
+
SHIELD_SECRET_KEY: {
|
|
3584
|
+
"runtime": "server",
|
|
3585
|
+
"kind": "nonempty",
|
|
3586
|
+
"required": true,
|
|
3587
|
+
"example": "shield_secret"
|
|
3588
|
+
},
|
|
3589
|
+
SHIELD_ENCRYPTION_SHARE: {
|
|
3590
|
+
"runtime": "server",
|
|
3591
|
+
"kind": "nonempty",
|
|
3592
|
+
"required": true,
|
|
3593
|
+
"example": "shield_share"
|
|
3594
|
+
},
|
|
3595
|
+
FEE_SPONSORSHIP_ID: {
|
|
3596
|
+
"runtime": "server",
|
|
3597
|
+
"kind": "nonempty",
|
|
3598
|
+
"required": true,
|
|
3599
|
+
"example": "fee_sponsor"
|
|
3600
|
+
},
|
|
3601
|
+
RESEND_API_KEY: {
|
|
3602
|
+
"runtime": "server",
|
|
3603
|
+
"kind": "nonempty",
|
|
3604
|
+
"required": false,
|
|
3605
|
+
"example": "re_test"
|
|
3606
|
+
},
|
|
3607
|
+
BREVO_API_KEY: {
|
|
3608
|
+
"runtime": "server",
|
|
3609
|
+
"kind": "nonempty",
|
|
3610
|
+
"required": false,
|
|
3611
|
+
"example": "brevo_test"
|
|
3612
|
+
},
|
|
3613
|
+
AUTH_MODE: {
|
|
3614
|
+
"runtime": "server",
|
|
3615
|
+
"kind": "authMode",
|
|
3616
|
+
"required": false,
|
|
3617
|
+
"example": "development"
|
|
3618
|
+
},
|
|
3619
|
+
AUTH_DEV_OTP: {
|
|
3620
|
+
"runtime": "server",
|
|
3621
|
+
"kind": "nonempty",
|
|
3622
|
+
"required": false,
|
|
3623
|
+
"example": "000000"
|
|
3624
|
+
},
|
|
3625
|
+
SENTRY_DSN: {
|
|
3626
|
+
"runtime": "server",
|
|
3627
|
+
"kind": "url",
|
|
3628
|
+
"required": false,
|
|
3629
|
+
"example": "https://public@example.ingest.sentry.io/1"
|
|
3630
|
+
},
|
|
3631
|
+
POSTHOG_API_KEY: {
|
|
3632
|
+
"runtime": "server",
|
|
3633
|
+
"kind": "nonempty",
|
|
3634
|
+
"required": false,
|
|
3635
|
+
"example": "phx_test"
|
|
3636
|
+
},
|
|
3637
|
+
POSTHOG_PROJECT_TOKEN: {
|
|
3638
|
+
"runtime": "server",
|
|
3639
|
+
"kind": "nonempty",
|
|
3640
|
+
"required": false,
|
|
3641
|
+
"example": "phc_test"
|
|
3642
|
+
},
|
|
3643
|
+
POSTHOG_INGEST_KEY: {
|
|
3644
|
+
"runtime": "server",
|
|
3645
|
+
"kind": "nonempty",
|
|
3646
|
+
"required": false,
|
|
3647
|
+
"example": "phc_test"
|
|
3648
|
+
},
|
|
3649
|
+
POSTHOG_HOST: {
|
|
3650
|
+
"runtime": "server",
|
|
3651
|
+
"kind": "url",
|
|
3652
|
+
"required": false,
|
|
3653
|
+
"example": "https://us.i.posthog.com"
|
|
3654
|
+
},
|
|
3655
|
+
POSTHOG_PERSONAL_API_KEY: {
|
|
3656
|
+
"runtime": "server",
|
|
3657
|
+
"kind": "nonempty",
|
|
3658
|
+
"required": false,
|
|
3659
|
+
"example": "phx_personal"
|
|
3660
|
+
},
|
|
3661
|
+
POSTHOG_PROJECT_ID: {
|
|
3662
|
+
"runtime": "server",
|
|
3663
|
+
"kind": "nonempty",
|
|
3664
|
+
"required": false,
|
|
3665
|
+
"example": "12345"
|
|
3666
|
+
},
|
|
3667
|
+
POSTHOG_ENV_ID: {
|
|
3668
|
+
"runtime": "server",
|
|
3669
|
+
"kind": "nonempty",
|
|
3670
|
+
"required": false,
|
|
3671
|
+
"example": "12345"
|
|
3672
|
+
},
|
|
3673
|
+
SENDER_EMAIL: {
|
|
3674
|
+
"runtime": "server",
|
|
3675
|
+
"kind": "email",
|
|
3676
|
+
"required": false,
|
|
3677
|
+
"example": "noreply@capxul.test"
|
|
3678
|
+
},
|
|
3679
|
+
SHIELD_API_URL: {
|
|
3680
|
+
"runtime": "server",
|
|
3681
|
+
"kind": "url",
|
|
3682
|
+
"required": false,
|
|
3683
|
+
"example": "https://api.shield.test"
|
|
3684
|
+
},
|
|
3685
|
+
ALCHEMY_API_KEY: {
|
|
3686
|
+
"runtime": "server",
|
|
3687
|
+
"kind": "nonempty",
|
|
3688
|
+
"required": false,
|
|
3689
|
+
"example": "alchemy_test"
|
|
3690
|
+
},
|
|
3691
|
+
ALCHEMY_GAS_POLICY_ID: {
|
|
3692
|
+
"runtime": "server",
|
|
3693
|
+
"kind": "nonempty",
|
|
3694
|
+
"required": false,
|
|
3695
|
+
"example": "policy_test"
|
|
3696
|
+
},
|
|
3697
|
+
ALCHEMY_WEBHOOK_SIGNING_KEY: {
|
|
3698
|
+
"runtime": "server",
|
|
3699
|
+
"kind": "nonempty",
|
|
3700
|
+
"required": true,
|
|
3701
|
+
"example": "alchemy_hmac"
|
|
3702
|
+
},
|
|
3703
|
+
ALCHEMY_AUTH_TOKEN: {
|
|
3704
|
+
"runtime": "server",
|
|
3705
|
+
"kind": "nonempty",
|
|
3706
|
+
"required": false,
|
|
3707
|
+
"example": "alchemy_auth"
|
|
3708
|
+
},
|
|
3709
|
+
ALCHEMY_WEBHOOK_ID: {
|
|
3710
|
+
"runtime": "server",
|
|
3711
|
+
"kind": "nonempty",
|
|
3712
|
+
"required": false,
|
|
3713
|
+
"example": "wh_123"
|
|
3714
|
+
},
|
|
3715
|
+
GOLDSKY_WEBHOOK_SECRET: {
|
|
3716
|
+
"runtime": "server",
|
|
3717
|
+
"kind": "nonempty",
|
|
3718
|
+
"required": false,
|
|
3719
|
+
"example": "goldsky_secret"
|
|
3720
|
+
},
|
|
3721
|
+
DEPLOYER_PRIVATE_KEY: {
|
|
3722
|
+
"runtime": "server",
|
|
3723
|
+
"kind": "privateKey",
|
|
3724
|
+
"required": true,
|
|
3725
|
+
"example": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
3726
|
+
},
|
|
3727
|
+
BASE_SEPOLIA_RPC_URL: {
|
|
3728
|
+
"runtime": "server",
|
|
3729
|
+
"kind": "url",
|
|
3730
|
+
"required": true,
|
|
3731
|
+
"example": "https://sepolia.base.org"
|
|
3732
|
+
},
|
|
3733
|
+
CONVEX_SITE_URL: {
|
|
3734
|
+
"runtime": "server",
|
|
3735
|
+
"kind": "url",
|
|
3736
|
+
"required": false,
|
|
3737
|
+
"example": "https://example.convex.site"
|
|
3738
|
+
},
|
|
3739
|
+
AGENTMAIL_API_KEY: {
|
|
3740
|
+
"runtime": "server",
|
|
3741
|
+
"kind": "nonempty",
|
|
3742
|
+
"required": false,
|
|
3743
|
+
"example": "agentmail_test"
|
|
3744
|
+
},
|
|
3745
|
+
CAPXUL_E2E_PUBLISHABLE_KEY: {
|
|
3746
|
+
"runtime": "server",
|
|
3747
|
+
"kind": "nonempty",
|
|
3748
|
+
"required": false,
|
|
3749
|
+
"example": "cap_pk_test_00000000000000000000000000000000"
|
|
3750
|
+
},
|
|
3751
|
+
CAPXUL_E2E_ORIGIN: {
|
|
3752
|
+
"runtime": "server",
|
|
3753
|
+
"kind": "url",
|
|
3754
|
+
"required": false,
|
|
3755
|
+
"example": "https://e2e.test.capxul.local"
|
|
3756
|
+
},
|
|
3757
|
+
CAPXUL_E2E_REQUIRED: {
|
|
3758
|
+
"runtime": "server",
|
|
3759
|
+
"kind": "nonempty",
|
|
3760
|
+
"required": false,
|
|
3761
|
+
"example": "1"
|
|
3762
|
+
},
|
|
3763
|
+
CAPXUL_FAUCET_ENABLED: {
|
|
3764
|
+
"runtime": "server",
|
|
3765
|
+
"kind": "nonempty",
|
|
3766
|
+
"required": false,
|
|
3767
|
+
"example": "1"
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3770
|
+
function schemaFor(kind) {
|
|
3771
|
+
switch (kind) {
|
|
3772
|
+
case "authMode": return Schema.String.pipe(Schema.filter((value) => value === "development" || value === "production" || value === "test", { message: () => "Invalid enum value" }));
|
|
3773
|
+
case "email": return Schema.String.pipe(Schema.filter((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), { message: () => "Invalid email" }));
|
|
3774
|
+
case "nonempty": return Schema.String.pipe(Schema.minLength(1));
|
|
3775
|
+
case "privateKey": return Schema.String.pipe(Schema.filter((value) => /^0x[0-9a-fA-F]{64}$/.test(value), { message: () => "Must be a 0x-prefixed 32-byte hex private key" }));
|
|
3776
|
+
case "url": return Schema.String.pipe(Schema.filter((value) => {
|
|
3777
|
+
try {
|
|
3778
|
+
return new URL(value).href.length > 0;
|
|
3779
|
+
} catch {
|
|
3780
|
+
return false;
|
|
3781
|
+
}
|
|
3782
|
+
}, { message: () => "Invalid url" }));
|
|
3783
|
+
default: {
|
|
3784
|
+
const unsupported = kind;
|
|
3785
|
+
throw Errors.invalidInput(String(unsupported), "unsupported environment field kind");
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
const envShapeFields = Object.fromEntries(Object.entries(envFields).map(([key, field]) => [key, field.required ? schemaFor(field.kind) : Schema.optional(schemaFor(field.kind))]));
|
|
3790
|
+
const envShapeSchema = Schema.Struct(envShapeFields);
|
|
3791
|
+
Schema.partial(envShapeSchema);
|
|
3792
|
+
function parseEnvValue$1(key, raw, required) {
|
|
3793
|
+
const field = envFields[key];
|
|
3794
|
+
if (field === void 0) throw Errors.invalidInput(key, "unknown environment key");
|
|
3795
|
+
const normalized = normalizeEnvRaw(raw);
|
|
3796
|
+
if (normalized === void 0) {
|
|
3797
|
+
if (required) throw Errors.envMissing(key);
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
const result = Schema.decodeUnknownEither(schemaFor(field.kind))(normalized);
|
|
3801
|
+
if (Either.isLeft(result)) throw Errors.invalidInput(key, TreeFormatter.formatErrorSync(result.left));
|
|
3802
|
+
return result.right;
|
|
3803
|
+
}
|
|
3804
|
+
function normalizeEnvRaw(raw) {
|
|
3805
|
+
if (raw === void 0 || raw.trim() === "") return;
|
|
3806
|
+
return raw;
|
|
3807
|
+
}
|
|
3808
|
+
function runtimeEnv() {
|
|
3809
|
+
return globalThis.process?.env ?? {};
|
|
3810
|
+
}
|
|
3811
|
+
//#endregion
|
|
3812
|
+
//#region src/adapters/env/_shared.ts
|
|
3813
|
+
function parseEnvValue(key, raw, required) {
|
|
3814
|
+
return parseEnvValue$1(key, raw, required);
|
|
3815
|
+
}
|
|
3816
|
+
function hasEnvValue(key, raw) {
|
|
3817
|
+
if (envFields[key] === void 0) throw envErrorFromCapxul(key, Errors.invalidInput(key, "unknown environment key"));
|
|
3818
|
+
return normalizeEnvRaw(raw) !== void 0;
|
|
3819
|
+
}
|
|
3820
|
+
function catchEnvError(key, cause) {
|
|
3821
|
+
if (cause instanceof EnvError) return cause;
|
|
3822
|
+
if (isCapxulError(cause)) return envErrorFromCapxul(key, cause, cause);
|
|
3823
|
+
const redactedCause = /* @__PURE__ */ new Error(`env ${key} read failed`);
|
|
3824
|
+
return envErrorFromCapxul(key, Errors.providerError("env", key, redactedCause), redactedCause);
|
|
3825
|
+
}
|
|
3826
|
+
//#endregion
|
|
3827
|
+
//#region src/adapters/env/SystemEnvAdapter.ts
|
|
3828
|
+
var SystemEnvAdapter = class {
|
|
3829
|
+
#source;
|
|
3830
|
+
constructor(deps = {}) {
|
|
3831
|
+
this.#source = deps.source ?? runtimeEnv();
|
|
3832
|
+
}
|
|
3833
|
+
require = (key) => Effect.try({
|
|
3834
|
+
try: () => parseEnvValue(key, this.#source[key], true),
|
|
3835
|
+
catch: (cause) => catchEnvError(key, cause)
|
|
3836
|
+
});
|
|
3837
|
+
optional = (key) => Effect.try({
|
|
3838
|
+
try: () => parseEnvValue(key, this.#source[key], false),
|
|
3839
|
+
catch: (cause) => catchEnvError(key, cause)
|
|
3840
|
+
});
|
|
3841
|
+
has = (key) => Effect.try({
|
|
3842
|
+
try: () => hasEnvValue(key, this.#source[key]),
|
|
3843
|
+
catch: (cause) => catchEnvError(key, cause)
|
|
3844
|
+
});
|
|
3845
|
+
};
|
|
3846
|
+
function SystemEnvLayer(deps = {}) {
|
|
3847
|
+
return Layer.succeed(EnvPortTag, new SystemEnvAdapter(deps));
|
|
3848
|
+
}
|
|
3849
|
+
//#endregion
|
|
3850
|
+
//#region src/ports/evm-signer.ts
|
|
3851
|
+
var EvmSignerError = class extends Data.TaggedError("EvmSignerError") {};
|
|
3852
|
+
function evmSignerErrorFromCapxul(operation, error, cause = error) {
|
|
3853
|
+
return new EvmSignerError({
|
|
3854
|
+
publicError: error,
|
|
3855
|
+
publicCode: error.code,
|
|
3856
|
+
operation,
|
|
3857
|
+
...error.details === void 0 ? {} : { details: error.details },
|
|
3858
|
+
cause
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
var EvmSignerPortTag = class extends Context.Tag("@capxul/sdk/ports/EvmSignerPort")() {};
|
|
3862
|
+
//#endregion
|
|
3863
|
+
//#region src/adapters/evm-signer/UnavailableEvmSignerAdapter.ts
|
|
3864
|
+
var UnavailableEvmSignerAdapter = class {
|
|
3865
|
+
getAddress = Effect.fail(evmSignerErrorFromCapxul("getAddress", Errors.notImplemented("evmSigner", "getAddress")));
|
|
3866
|
+
signMessage(_message) {
|
|
3867
|
+
return Effect.fail(evmSignerErrorFromCapxul("signMessage", Errors.notImplemented("evmSigner", "signMessage")));
|
|
3868
|
+
}
|
|
3869
|
+
signUserOp(_userOperation) {
|
|
3870
|
+
return Effect.fail(evmSignerErrorFromCapxul("signUserOp", Errors.notImplemented("evmSigner", "signUserOp")));
|
|
3871
|
+
}
|
|
3872
|
+
};
|
|
3873
|
+
function UnavailableEvmSignerLayer() {
|
|
3874
|
+
return Layer.effect(EvmSignerPortTag, Effect.succeed(new UnavailableEvmSignerAdapter()));
|
|
3875
|
+
}
|
|
3876
|
+
//#endregion
|
|
3877
|
+
//#region src/adapters/convex-identity.ts
|
|
3878
|
+
const identityLoadByAuthUserIdQuery = makeFunctionReference("identity/queries:loadByAuthUserId");
|
|
3879
|
+
const identityCreateMutation = makeFunctionReference("identity/mutations:create");
|
|
3880
|
+
const identityUpdateMutation = makeFunctionReference("identity/mutations:update");
|
|
3881
|
+
var ConvexIdentityAdapter = class {
|
|
3882
|
+
#convex;
|
|
3883
|
+
constructor(deps) {
|
|
3884
|
+
this.#convex = deps.convex;
|
|
3885
|
+
}
|
|
3886
|
+
loadByAuthUserId(authUserId) {
|
|
3887
|
+
return this.#convex.query(identityLoadByAuthUserIdQuery, { authUserId }).pipe(Effect.mapError((error) => identityErrorFromCapxul("loadByAuthUserId", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3888
|
+
try: () => row === null ? null : brandProfile(row),
|
|
3889
|
+
catch: (cause) => identityErrorFromUnknown("loadByAuthUserId", cause)
|
|
3890
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(identityErrorFromUnknown("loadByAuthUserId", cause))));
|
|
3891
|
+
}
|
|
3892
|
+
create(input) {
|
|
3893
|
+
return this.#convex.mutation(identityCreateMutation, input).pipe(Effect.mapError((error) => identityErrorFromCapxul("create", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3894
|
+
try: () => brandProfile(row),
|
|
3895
|
+
catch: (cause) => identityErrorFromUnknown("create", cause)
|
|
3896
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(identityErrorFromUnknown("create", cause))));
|
|
3897
|
+
}
|
|
3898
|
+
update(input) {
|
|
3899
|
+
return this.#convex.mutation(identityUpdateMutation, input).pipe(Effect.mapError((error) => identityErrorFromCapxul("update", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
3900
|
+
try: () => brandProfile(row),
|
|
3901
|
+
catch: (cause) => identityErrorFromUnknown("update", cause)
|
|
3902
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(identityErrorFromUnknown("update", cause))));
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
function ConvexIdentityLayer() {
|
|
3906
|
+
return Layer.effect(IdentityPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexIdentityAdapter({ convex })));
|
|
3907
|
+
}
|
|
3908
|
+
function brandProfile(raw) {
|
|
3909
|
+
return {
|
|
3910
|
+
authUserId: toAuthUserId(raw.authUserId),
|
|
3911
|
+
email: toEmail(raw.email),
|
|
3912
|
+
displayName: raw.displayName,
|
|
3913
|
+
country: raw.country === null ? null : toCountryCode(raw.country),
|
|
3914
|
+
kycTier: toKycTier(raw.kycTier),
|
|
3915
|
+
createdAt: toEpochMs(raw.createdAt),
|
|
3916
|
+
updatedAt: toEpochMs(raw.updatedAt)
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
function identityErrorFromUnknown(operation, cause) {
|
|
3920
|
+
if (cause instanceof CapxulError) return identityErrorFromCapxul(operation, cause);
|
|
3921
|
+
return identityErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
|
|
3922
|
+
}
|
|
3923
|
+
//#endregion
|
|
3924
|
+
//#region src/ports/account-read.ts
|
|
3925
|
+
var AccountReadError = class extends Data.TaggedError("AccountReadError") {};
|
|
3926
|
+
function accountReadErrorFromCapxul(operation, error, cause = error) {
|
|
3927
|
+
return new AccountReadError({
|
|
3928
|
+
operation,
|
|
3929
|
+
publicCode: error.code,
|
|
3930
|
+
publicError: error,
|
|
3931
|
+
cause,
|
|
3932
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3935
|
+
var AccountReadPortTag = class extends Context.Tag("@capxul/sdk/ports/AccountReadPort")() {};
|
|
3936
|
+
//#endregion
|
|
3937
|
+
//#region src/adapters/account-read/ConvexAccountAdapter.ts
|
|
3938
|
+
const DEFAULT_FUNCTIONS$2 = {
|
|
3939
|
+
readBalance: makeFunctionReference("account/actions:readBalance"),
|
|
3940
|
+
faucetMint: makeFunctionReference("account/actions:faucetMint")
|
|
3941
|
+
};
|
|
3942
|
+
var ConvexAccountAdapter = class {
|
|
3943
|
+
#convex;
|
|
3944
|
+
#fns;
|
|
3945
|
+
constructor(deps) {
|
|
3946
|
+
this.#convex = deps.convex;
|
|
3947
|
+
this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$2;
|
|
3948
|
+
}
|
|
3949
|
+
readBalance(input) {
|
|
3950
|
+
return this.#convex.action(this.#fns.readBalance, { chainId: wireChainId(input.chainId) }).pipe(Effect.mapError((error) => accountReadErrorFromCapxul("readBalance", error.publicError, error)), Effect.flatMap((wire) => brandAccountEffect("readBalance", wire)), Effect.catchAllDefect((cause) => Effect.fail(accountReadErrorFromUnknown("readBalance", cause))));
|
|
3951
|
+
}
|
|
3952
|
+
fundFromFaucet(input) {
|
|
3953
|
+
const operation = "fundFromFaucet";
|
|
3954
|
+
return Effect.suspend(() => {
|
|
3955
|
+
const rawAmount = toWei(input.amount);
|
|
3956
|
+
return this.#convex.action(this.#fns.faucetMint, {
|
|
3957
|
+
chainId: wireChainId(input.chainId),
|
|
3958
|
+
rawAmount
|
|
3959
|
+
});
|
|
3960
|
+
}).pipe(Effect.mapError((error) => accountReadErrorFromCapxul(operation, error.publicError, error)), Effect.map((wire) => ({ txHash: wire.txHash })), Effect.catchAllDefect((cause) => Effect.fail(accountReadErrorFromUnknown(operation, cause))));
|
|
3961
|
+
}
|
|
3962
|
+
};
|
|
3963
|
+
function ConvexAccountLayer() {
|
|
3964
|
+
return Layer.effect(AccountReadPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexAccountAdapter({ convex })));
|
|
3965
|
+
}
|
|
3966
|
+
function brandAccountEffect(operation, wire) {
|
|
3967
|
+
return Effect.try({
|
|
3968
|
+
try: () => brandAccount(wire),
|
|
3969
|
+
catch: (cause) => accountReadErrorFromUnknown(operation, cause)
|
|
3970
|
+
});
|
|
3971
|
+
}
|
|
3972
|
+
function brandAccount(wire) {
|
|
3973
|
+
const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
|
|
3974
|
+
const available = fromWei(wire.rawAvailableBalance ?? wire.rawBalance, wire.decimals, wire.currency);
|
|
3975
|
+
return {
|
|
3976
|
+
id: toAccountId(wire.accountId),
|
|
3977
|
+
balance,
|
|
3978
|
+
available
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
function accountReadErrorFromUnknown(operation, cause) {
|
|
3982
|
+
if (cause instanceof CapxulError) return accountReadErrorFromCapxul(operation, cause);
|
|
3983
|
+
return accountReadErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
|
|
3984
|
+
}
|
|
3985
|
+
//#endregion
|
|
3986
|
+
//#region src/ports/sub-account.ts
|
|
3987
|
+
var SubAccountError = class extends Data.TaggedError("SubAccountError") {};
|
|
3988
|
+
function subAccountErrorFromCapxul(operation, error, cause = error) {
|
|
3989
|
+
return new SubAccountError({
|
|
3990
|
+
operation,
|
|
3991
|
+
publicCode: error.code,
|
|
3992
|
+
publicError: error,
|
|
3993
|
+
cause,
|
|
3994
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
3997
|
+
var SubAccountPortTag = class extends Context.Tag("@capxul/sdk/ports/SubAccountPort")() {};
|
|
3998
|
+
//#endregion
|
|
3999
|
+
//#region src/adapters/sub-account/ConvexSubAccountAdapter.ts
|
|
4000
|
+
const DEFAULT_FUNCTIONS$1 = {
|
|
4001
|
+
create: makeFunctionReference("subAccount/mutations:create"),
|
|
4002
|
+
get: makeFunctionReference("subAccount/queries:get"),
|
|
4003
|
+
list: makeFunctionReference("subAccount/queries:list"),
|
|
4004
|
+
rename: makeFunctionReference("subAccount/mutations:rename"),
|
|
4005
|
+
remove: makeFunctionReference("subAccount/mutations:remove"),
|
|
4006
|
+
transfer: makeFunctionReference("subAccount/actions:transfer")
|
|
4007
|
+
};
|
|
4008
|
+
var ConvexSubAccountAdapter = class {
|
|
4009
|
+
#convex;
|
|
4010
|
+
#fns;
|
|
4011
|
+
#chainId;
|
|
4012
|
+
constructor(deps) {
|
|
4013
|
+
this.#convex = deps.convex;
|
|
4014
|
+
this.#chainId = deps.chainId;
|
|
4015
|
+
this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$1;
|
|
4016
|
+
}
|
|
4017
|
+
create(input) {
|
|
4018
|
+
return this.#runMutation("create", this.#fns.create, {
|
|
4019
|
+
accountId: input.accountId,
|
|
4020
|
+
name: input.name
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
get(input) {
|
|
4024
|
+
return this.#runQuery("get", this.#fns.get, { subAccountId: input.subAccountId }, (wire) => wire === null ? null : brandSubAccount(wire));
|
|
4025
|
+
}
|
|
4026
|
+
list(input) {
|
|
4027
|
+
return this.#runQuery("list", this.#fns.list, { accountId: input.accountId }, (wires) => wires.map((wire) => brandSubAccount(wire)));
|
|
4028
|
+
}
|
|
4029
|
+
rename(input) {
|
|
4030
|
+
return this.#runMutation("rename", this.#fns.rename, {
|
|
4031
|
+
subAccountId: input.subAccountId,
|
|
4032
|
+
name: input.name
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
delete(input) {
|
|
4036
|
+
const operation = "delete";
|
|
4037
|
+
return this.#convex.mutation(this.#fns.remove, { subAccountId: input.subAccountId }).pipe(Effect.mapError((error) => subAccountErrorFromCapxul(operation, error.publicError, error)), Effect.asVoid, Effect.catchAllDefect((cause) => Effect.fail(subAccountErrorFromUnknown(operation, cause))));
|
|
4038
|
+
}
|
|
4039
|
+
transfer(input) {
|
|
4040
|
+
const operation = "transfer";
|
|
4041
|
+
return Effect.suspend(() => {
|
|
4042
|
+
const rawAmount = toWei(input.amount);
|
|
4043
|
+
return this.#convex.action(this.#fns.transfer, {
|
|
4044
|
+
chainId: this.#chainId,
|
|
4045
|
+
from: input.from,
|
|
4046
|
+
to: input.to,
|
|
4047
|
+
rawAmount
|
|
4048
|
+
});
|
|
4049
|
+
}).pipe(Effect.mapError((error) => subAccountErrorFromCapxul(operation, error.publicError, error)), Effect.map((wire) => brandTransferResult(wire)), Effect.catchAllDefect((cause) => Effect.fail(subAccountErrorFromUnknown(operation, cause))));
|
|
4050
|
+
}
|
|
4051
|
+
#runMutation(operation, ref, args) {
|
|
4052
|
+
return this.#convex.mutation(ref, args).pipe(Effect.mapError((error) => subAccountErrorFromCapxul(operation, error.publicError, error)), Effect.map((wire) => brandSubAccount(wire)), Effect.catchAllDefect((cause) => Effect.fail(subAccountErrorFromUnknown(operation, cause))));
|
|
4053
|
+
}
|
|
4054
|
+
#runQuery(operation, ref, args, map) {
|
|
4055
|
+
return this.#convex.query(ref, args).pipe(Effect.mapError((error) => subAccountErrorFromCapxul(operation, error.publicError, error)), Effect.map(map), Effect.catchAllDefect((cause) => Effect.fail(subAccountErrorFromUnknown(operation, cause))));
|
|
4056
|
+
}
|
|
4057
|
+
};
|
|
4058
|
+
function ConvexSubAccountLayer(chainId) {
|
|
4059
|
+
return Layer.effect(SubAccountPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexSubAccountAdapter({
|
|
4060
|
+
convex,
|
|
4061
|
+
chainId
|
|
4062
|
+
})));
|
|
4063
|
+
}
|
|
4064
|
+
function brandTransferResult(wire) {
|
|
4065
|
+
return {
|
|
4066
|
+
available: fromWei(wire.availableRaw, wire.decimals, wire.currency),
|
|
4067
|
+
from: wire.from === null ? null : brandSubAccount(wire.from),
|
|
4068
|
+
to: wire.to === null ? null : brandSubAccount(wire.to)
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
function brandSubAccount(wire) {
|
|
4072
|
+
return {
|
|
4073
|
+
id: toSubAccountId(wire.subAccountId),
|
|
4074
|
+
accountId: toAccountId(wire.accountId),
|
|
4075
|
+
name: wire.name,
|
|
4076
|
+
balance: fromWei(wire.rawBalance, wire.decimals, wire.currency),
|
|
4077
|
+
createdAt: toEpochMs(wire.createdAt)
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
function subAccountErrorFromUnknown(operation, cause) {
|
|
4081
|
+
if (cause instanceof CapxulError) return subAccountErrorFromCapxul(operation, cause);
|
|
4082
|
+
return subAccountErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
|
|
4083
|
+
}
|
|
4084
|
+
//#endregion
|
|
4085
|
+
//#region src/ports/smart-account.ts
|
|
4086
|
+
var SmartAccountError = class extends Data.TaggedError("SmartAccountError") {};
|
|
4087
|
+
function smartAccountErrorFromCapxul(operation, error, cause = error) {
|
|
4088
|
+
return new SmartAccountError({
|
|
4089
|
+
operation,
|
|
4090
|
+
publicCode: error.code,
|
|
4091
|
+
publicError: error,
|
|
4092
|
+
cause,
|
|
4093
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
4094
|
+
});
|
|
4095
|
+
}
|
|
4096
|
+
var SmartAccountPortTag = class extends Context.Tag("@capxul/sdk/ports/SmartAccountPort")() {};
|
|
4097
|
+
//#endregion
|
|
4098
|
+
//#region src/adapters/smart-account/ConvexSmartAccountAdapter.ts
|
|
4099
|
+
const DEFAULT_FUNCTIONS = {
|
|
4100
|
+
loadByAuthUserId: makeFunctionReference("smartAccount/queries:loadByAuthUserId"),
|
|
4101
|
+
loadBySmartAccountAddress: makeFunctionReference("smartAccount/queries:loadBySmartAccountAddress"),
|
|
4102
|
+
provision: makeFunctionReference("smartAccount/mutations:provision"),
|
|
4103
|
+
confirmDeployment: makeFunctionReference("smartAccount/actions:confirmDeployment"),
|
|
4104
|
+
deployPrepare: makeFunctionReference("smartAccount/actions:deployPrepare"),
|
|
4105
|
+
deploySubmit: makeFunctionReference("smartAccount/actions:deploySubmit")
|
|
4106
|
+
};
|
|
4107
|
+
var ConvexSmartAccountAdapter = class {
|
|
4108
|
+
#convex;
|
|
4109
|
+
#fns;
|
|
4110
|
+
constructor(deps) {
|
|
4111
|
+
this.#convex = deps.convex;
|
|
4112
|
+
this.#fns = deps.functions ?? DEFAULT_FUNCTIONS;
|
|
4113
|
+
}
|
|
4114
|
+
loadByAuthUserId(authUserId) {
|
|
4115
|
+
return this.#convex.query(this.#fns.loadByAuthUserId, { authUserId }).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("loadByAuthUserId", error.publicError, error)), Effect.flatMap((row) => brandSmartAccountEffect("loadByAuthUserId", row)), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("loadByAuthUserId", cause))));
|
|
4116
|
+
}
|
|
4117
|
+
loadBySmartAccountAddress(address) {
|
|
4118
|
+
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
|
+
}
|
|
4120
|
+
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({
|
|
4125
|
+
try: () => brandProvisionedSmartAccount(input.authUserId, row),
|
|
4126
|
+
catch: (cause) => smartAccountErrorFromUnknown("provision", cause)
|
|
4127
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("provision", cause))));
|
|
4128
|
+
}
|
|
4129
|
+
confirmDeployment(input) {
|
|
4130
|
+
const evidence = {
|
|
4131
|
+
chainId: wireChainId(input.evidence.chainId),
|
|
4132
|
+
signerAddress: input.evidence.signerAddress,
|
|
4133
|
+
safeAddress: input.evidence.safeAddress,
|
|
4134
|
+
...input.evidence.userOpHash === void 0 ? {} : { userOpHash: input.evidence.userOpHash },
|
|
4135
|
+
...input.evidence.txHash === void 0 ? {} : { txHash: input.evidence.txHash },
|
|
4136
|
+
...input.evidence.blockNumber === void 0 ? {} : { blockNumber: input.evidence.blockNumber }
|
|
4137
|
+
};
|
|
4138
|
+
return this.#convex.action(this.#fns.confirmDeployment, {
|
|
4139
|
+
chainId: wireChainId(input.chainId),
|
|
4140
|
+
safeAddress: input.safeAddress,
|
|
4141
|
+
...input.telemetryRunId === void 0 ? {} : { telemetryRunId: input.telemetryRunId },
|
|
4142
|
+
evidence
|
|
4143
|
+
}).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("confirmDeployment", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
4144
|
+
try: () => brandProvisionedSmartAccount(input.authUserId, row),
|
|
4145
|
+
catch: (cause) => smartAccountErrorFromUnknown("confirmDeployment", cause)
|
|
4146
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("confirmDeployment", cause))));
|
|
4147
|
+
}
|
|
4148
|
+
deployPrepare(input) {
|
|
4149
|
+
return this.#convex.action(this.#fns.deployPrepare, {
|
|
4150
|
+
chainId: wireChainId(input.chainId),
|
|
4151
|
+
...input.telemetryRunId === void 0 ? {} : { telemetryRunId: input.telemetryRunId }
|
|
4152
|
+
}).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("deployPrepare", error.publicError, error)), Effect.flatMap((result) => Effect.try({
|
|
4153
|
+
try: () => {
|
|
4154
|
+
if (typeof result.digest !== "string" || !isHex(result.digest) || result.digest.length !== 66) throw Errors.invalidInput("digest", "must be 0x-prefixed 32-byte hex");
|
|
4155
|
+
return {
|
|
4156
|
+
digest: result.digest,
|
|
4157
|
+
safeAddress: toAddress(result.safeAddress),
|
|
4158
|
+
userOp: result.userOp
|
|
4159
|
+
};
|
|
4160
|
+
},
|
|
4161
|
+
catch: (cause) => smartAccountErrorFromUnknown("deployPrepare", cause)
|
|
4162
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("deployPrepare", cause))));
|
|
4163
|
+
}
|
|
4164
|
+
deploySubmit(input) {
|
|
4165
|
+
return this.#convex.action(this.#fns.deploySubmit, {
|
|
4166
|
+
chainId: wireChainId(input.chainId),
|
|
4167
|
+
signature: input.signature,
|
|
4168
|
+
userOp: input.userOp,
|
|
4169
|
+
...input.telemetryRunId === void 0 ? {} : { telemetryRunId: input.telemetryRunId }
|
|
4170
|
+
}).pipe(Effect.mapError((error) => smartAccountErrorFromCapxul("deploySubmit", error.publicError, error)), Effect.flatMap((row) => Effect.try({
|
|
4171
|
+
try: () => brandProvisionedSmartAccount(input.authUserId, row),
|
|
4172
|
+
catch: (cause) => smartAccountErrorFromUnknown("deploySubmit", cause)
|
|
4173
|
+
})), Effect.catchAllDefect((cause) => Effect.fail(smartAccountErrorFromUnknown("deploySubmit", cause))));
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
function ConvexSmartAccountLayer() {
|
|
4177
|
+
return Layer.effect(SmartAccountPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexSmartAccountAdapter({ convex })));
|
|
4178
|
+
}
|
|
4179
|
+
function brandSmartAccountEffect(operation, wire) {
|
|
4180
|
+
return Effect.try({
|
|
4181
|
+
try: () => wire === null ? null : brandNonNullSmartAccount(wire),
|
|
4182
|
+
catch: (cause) => smartAccountErrorFromUnknown(operation, cause)
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
function brandNonNullSmartAccount(wire) {
|
|
4186
|
+
return {
|
|
4187
|
+
authUserId: toAuthUserId(wire.authUserId),
|
|
4188
|
+
signerAddress: toAddress(wire.signerAddress),
|
|
4189
|
+
smartAccountAddress: toAddress(wire.smartAccountAddress),
|
|
4190
|
+
chainId: toChainId(wire.chainId),
|
|
4191
|
+
deployedAt: wire.deployedAt === null ? null : toEpochMs(wire.deployedAt),
|
|
4192
|
+
createdAt: toEpochMs(wire.createdAt)
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
4195
|
+
function brandProvisionedSmartAccount(requestedAuthUserId, wire) {
|
|
4196
|
+
if (wire.authUserId !== String(requestedAuthUserId)) throw Errors.notAuthenticated();
|
|
4197
|
+
return brandNonNullSmartAccount(wire);
|
|
4198
|
+
}
|
|
4199
|
+
function smartAccountErrorFromUnknown(operation, cause) {
|
|
4200
|
+
if (cause instanceof CapxulError) return smartAccountErrorFromCapxul(operation, cause);
|
|
4201
|
+
return smartAccountErrorFromCapxul(operation, Errors.providerError("convex", operation, cause), cause);
|
|
4202
|
+
}
|
|
4203
|
+
toAddress("0xa6b71e26c5e0845f74c812102ca7114b6a896ab2");
|
|
4204
|
+
keccak256("0x");
|
|
4205
|
+
`${"ab".repeat(32)}`;
|
|
4206
|
+
//#endregion
|
|
4207
|
+
//#region src/adapters/telemetry/PostHogTelemetryAdapter.ts
|
|
4208
|
+
var PostHogTelemetryAdapter = class {
|
|
4209
|
+
#capture;
|
|
4210
|
+
#identify;
|
|
4211
|
+
#group;
|
|
4212
|
+
#reset;
|
|
4213
|
+
constructor(deps) {
|
|
4214
|
+
this.#capture = deps.capture;
|
|
4215
|
+
this.#identify = deps.identify ?? (() => void 0);
|
|
4216
|
+
this.#group = deps.group ?? (() => void 0);
|
|
4217
|
+
this.#reset = deps.reset ?? (() => void 0);
|
|
4218
|
+
}
|
|
4219
|
+
emit(event) {
|
|
4220
|
+
return Effect.sync(() => {
|
|
4221
|
+
this.#runFireAndForget(() => this.#capture(event.name, redactTelemetryProps(event.name, cloneProps(event.props))));
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
4224
|
+
identify(input) {
|
|
4225
|
+
return Effect.sync(() => {
|
|
4226
|
+
this.#runFireAndForget(() => this.#identify(cloneIdentifyInput(input)));
|
|
4227
|
+
});
|
|
4228
|
+
}
|
|
4229
|
+
group(input) {
|
|
4230
|
+
return Effect.sync(() => {
|
|
4231
|
+
this.#runFireAndForget(() => this.#group(cloneGroupInput(input)));
|
|
4232
|
+
});
|
|
4233
|
+
}
|
|
4234
|
+
reset() {
|
|
4235
|
+
return Effect.sync(() => {
|
|
4236
|
+
this.#runFireAndForget(() => this.#reset());
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
#runFireAndForget(operation) {
|
|
4240
|
+
try {
|
|
4241
|
+
Promise.resolve(operation()).catch(() => {});
|
|
4242
|
+
} catch {}
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
function PostHogTelemetryLayer(deps) {
|
|
4246
|
+
return Layer.succeed(TelemetryPortTag, new PostHogTelemetryAdapter(deps));
|
|
4247
|
+
}
|
|
4248
|
+
function cloneIdentifyInput(input) {
|
|
4249
|
+
const traits = input.traits === void 0 ? void 0 : cloneProps(input.traits);
|
|
4250
|
+
const properties = input.properties === void 0 ? void 0 : cloneProps(input.properties);
|
|
4251
|
+
return {
|
|
4252
|
+
distinctId: input.distinctId,
|
|
4253
|
+
...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
|
|
4254
|
+
...traits === void 0 ? {} : { traits },
|
|
4255
|
+
...properties === void 0 ? {} : { properties }
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
function cloneGroupInput(input) {
|
|
4259
|
+
const properties = input.properties === void 0 ? void 0 : cloneProps(input.properties);
|
|
4260
|
+
return properties === void 0 ? {
|
|
4261
|
+
groupType: input.groupType,
|
|
4262
|
+
groupKey: input.groupKey
|
|
4263
|
+
} : {
|
|
4264
|
+
groupType: input.groupType,
|
|
4265
|
+
groupKey: input.groupKey,
|
|
4266
|
+
properties
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
4269
|
+
function cloneProps(props) {
|
|
4270
|
+
if (props === void 0) return void 0;
|
|
4271
|
+
const cloned = {};
|
|
4272
|
+
for (const [key, value] of Object.entries(props)) cloned[key] = cloneTelemetryValue(value);
|
|
4273
|
+
return cloned;
|
|
4274
|
+
}
|
|
4275
|
+
function cloneTelemetryValue(value) {
|
|
4276
|
+
if (Array.isArray(value)) return value.map(cloneTelemetryValue);
|
|
4277
|
+
if (value === null || typeof value !== "object") return value;
|
|
4278
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return value;
|
|
4279
|
+
const cloned = {};
|
|
4280
|
+
for (const [key, nested] of Object.entries(value)) cloned[key] = cloneTelemetryValue(nested);
|
|
4281
|
+
return cloned;
|
|
4282
|
+
}
|
|
4283
|
+
//#endregion
|
|
4284
|
+
//#region src/ports/transport.ts
|
|
4285
|
+
var TransportError = class extends Data.TaggedError("TransportError") {};
|
|
4286
|
+
function transportErrorFromCapxul(operation, request, error, cause = error) {
|
|
4287
|
+
return new TransportError({
|
|
4288
|
+
operation,
|
|
4289
|
+
name: request.name,
|
|
4290
|
+
publicCode: error.code,
|
|
4291
|
+
publicError: error,
|
|
4292
|
+
cause,
|
|
4293
|
+
...request.correlationId === void 0 ? {} : { correlationId: request.correlationId },
|
|
4294
|
+
...error.details === void 0 ? {} : { details: error.details }
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
var TransportPortTag = class extends Context.Tag("@capxul/sdk/ports/TransportPort")() {};
|
|
4298
|
+
//#endregion
|
|
4299
|
+
//#region src/adapters/transport/ConvexTransportAdapter.ts
|
|
4300
|
+
var ConvexTransportAdapter = class {
|
|
4301
|
+
#convex;
|
|
4302
|
+
constructor(convex) {
|
|
4303
|
+
this.#convex = convex;
|
|
4304
|
+
}
|
|
4305
|
+
runQuery(request) {
|
|
4306
|
+
return this.#call("query", request, () => {
|
|
4307
|
+
const fn = makeFunctionReference(request.name);
|
|
4308
|
+
return this.#convex.query(fn, asRecordArgs(request.args));
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
runMutation(request) {
|
|
4312
|
+
return this.#call("mutation", request, () => {
|
|
4313
|
+
const fn = makeFunctionReference(request.name);
|
|
4314
|
+
return this.#convex.mutation(fn, asRecordArgs(request.args));
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4317
|
+
runAction(request) {
|
|
4318
|
+
return this.#call("action", request, () => {
|
|
4319
|
+
const fn = makeFunctionReference(request.name);
|
|
4320
|
+
return this.#convex.action(fn, asRecordArgs(request.args));
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
#call(operation, request, run) {
|
|
4324
|
+
return Effect.try({
|
|
4325
|
+
try: run,
|
|
4326
|
+
catch: (cause) => transportErrorFromThrown(operation, request, cause)
|
|
4327
|
+
}).pipe(Effect.flatMap((effect) => effect.pipe(Effect.mapError((error) => transportErrorFromConvex(operation, request, error)), Effect.catchAllDefect((cause) => Effect.fail(transportErrorFromThrown(operation, request, cause))))));
|
|
4328
|
+
}
|
|
4329
|
+
};
|
|
4330
|
+
function ConvexTransportLayer(_deps = {}) {
|
|
4331
|
+
return Layer.effect(TransportPortTag, Effect.gen(function* () {
|
|
4332
|
+
return new ConvexTransportAdapter(yield* ConvexCallPortTag);
|
|
4333
|
+
}));
|
|
4334
|
+
}
|
|
4335
|
+
function transportErrorFromConvex(operation, request, error) {
|
|
4336
|
+
return transportErrorFromCapxul(operation, request, error.publicError, error.cause);
|
|
4337
|
+
}
|
|
4338
|
+
function asRecordArgs(args) {
|
|
4339
|
+
if (args !== null && typeof args === "object" && !Array.isArray(args)) return args;
|
|
4340
|
+
return { value: args };
|
|
4341
|
+
}
|
|
4342
|
+
function transportErrorFromThrown(operation, request, cause) {
|
|
4343
|
+
return transportErrorFromCapxul(operation, request, cause instanceof Error ? Errors.providerError("transport", request.name, cause) : Errors.providerError("transport", request.name, new Error(String(cause))), cause);
|
|
4344
|
+
}
|
|
4345
|
+
const SDK_VERSION = version;
|
|
4346
|
+
const collectProductionFlowPorts = Effect.gen(function* () {
|
|
4347
|
+
const authClient = yield* AuthClientPortTag;
|
|
4348
|
+
const authCache = yield* AuthCachePortTag;
|
|
4349
|
+
const bootstrap = yield* BootstrapPortTag;
|
|
4350
|
+
const clock = yield* ClockPortTag;
|
|
4351
|
+
const convexCall = yield* ConvexCallPortTag;
|
|
4352
|
+
const credentials = yield* CredentialsPortTag;
|
|
4353
|
+
const env = yield* EnvPortTag;
|
|
4354
|
+
const evmSigner = yield* EvmSignerPortTag;
|
|
4355
|
+
return {
|
|
4356
|
+
authClient,
|
|
4357
|
+
authCache,
|
|
4358
|
+
identity: yield* IdentityPortTag,
|
|
4359
|
+
smartAccount: yield* SmartAccountPortTag,
|
|
4360
|
+
accountRead: yield* AccountReadPortTag,
|
|
4361
|
+
subAccount: yield* SubAccountPortTag,
|
|
4362
|
+
credentials,
|
|
4363
|
+
bootstrap,
|
|
4364
|
+
evmSigner,
|
|
4365
|
+
clock,
|
|
4366
|
+
env,
|
|
4367
|
+
telemetry: yield* TelemetryPortTag,
|
|
4368
|
+
transport: yield* TransportPortTag,
|
|
4369
|
+
convexCall
|
|
4370
|
+
};
|
|
4371
|
+
});
|
|
4372
|
+
function makeProductionAdapterLayerEntries(input) {
|
|
4373
|
+
const refreshConvexAuthRef = {
|
|
4374
|
+
current: null,
|
|
4375
|
+
pending: false
|
|
4376
|
+
};
|
|
4377
|
+
return [
|
|
4378
|
+
{
|
|
4379
|
+
name: "bootstrap",
|
|
4380
|
+
layer: productionBootstrapPortLayer(input.bootstrap)
|
|
4381
|
+
},
|
|
4382
|
+
{
|
|
4383
|
+
name: "authClient",
|
|
4384
|
+
layer: productionAuthClientLayer(input, refreshConvexAuthRef)
|
|
4385
|
+
},
|
|
4386
|
+
{
|
|
4387
|
+
name: "authCache",
|
|
4388
|
+
layer: productionAuthCacheLayer(input)
|
|
4389
|
+
},
|
|
4390
|
+
{
|
|
4391
|
+
name: "identity",
|
|
4392
|
+
layer: ConvexIdentityLayer()
|
|
4393
|
+
},
|
|
4394
|
+
{
|
|
4395
|
+
name: "smartAccount",
|
|
4396
|
+
layer: ConvexSmartAccountLayer()
|
|
4397
|
+
},
|
|
4398
|
+
{
|
|
4399
|
+
name: "accountRead",
|
|
4400
|
+
layer: ConvexAccountLayer()
|
|
4401
|
+
},
|
|
4402
|
+
{
|
|
4403
|
+
name: "subAccount",
|
|
4404
|
+
layer: ConvexSubAccountLayer(input.chainId)
|
|
4405
|
+
},
|
|
4406
|
+
{
|
|
4407
|
+
name: "credentials",
|
|
4408
|
+
layer: ConvexCredentialsLayer()
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
name: "evmSigner",
|
|
4412
|
+
layer: input.evmSigner === void 0 ? UnavailableEvmSignerLayer() : Layer.succeed(EvmSignerPortTag, input.evmSigner)
|
|
4413
|
+
},
|
|
4414
|
+
{
|
|
4415
|
+
name: "clock",
|
|
4416
|
+
layer: SystemClockLayer()
|
|
4417
|
+
},
|
|
4418
|
+
{
|
|
4419
|
+
name: "env",
|
|
4420
|
+
layer: SystemEnvLayer()
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
name: "telemetry",
|
|
4424
|
+
layer: input.telemetry === void 0 ? PostHogTelemetryLayer({ capture: () => void 0 }) : Layer.succeed(TelemetryPortTag, input.telemetry)
|
|
4425
|
+
},
|
|
4426
|
+
{
|
|
4427
|
+
name: "transport",
|
|
4428
|
+
layer: ConvexTransportLayer()
|
|
4429
|
+
},
|
|
4430
|
+
{
|
|
4431
|
+
name: "convexCall",
|
|
4432
|
+
layer: productionConvexCallLayer(input, refreshConvexAuthRef)
|
|
4433
|
+
}
|
|
4434
|
+
];
|
|
4435
|
+
}
|
|
4436
|
+
function mergeProductionAdapterLayers(entries) {
|
|
4437
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4438
|
+
for (const entry of entries) byName.set(entry.name, entry.layer);
|
|
4439
|
+
const authClientLayer = byName.get("authClient");
|
|
4440
|
+
const convexCallLayer = byName.get("convexCall");
|
|
4441
|
+
const convexCallReadyLayer = convexCallLayer === void 0 || authClientLayer === void 0 ? convexCallLayer : convexCallLayer.pipe(Layer.provide(authClientLayer));
|
|
4442
|
+
return entries.map((entry) => {
|
|
4443
|
+
const layer = byName.get(entry.name) ?? entry.layer;
|
|
4444
|
+
if (entry.name === "convexCall") return convexCallReadyLayer ?? layer;
|
|
4445
|
+
if (isConvexDependentLayer(entry.name)) return convexCallReadyLayer === void 0 ? layer : layer.pipe(Layer.provide(convexCallReadyLayer));
|
|
4446
|
+
return layer;
|
|
4447
|
+
}).reduce((current, layer) => Layer.merge(current, layer), Layer.empty);
|
|
4448
|
+
}
|
|
4449
|
+
function isConvexDependentLayer(name) {
|
|
4450
|
+
return name === "identity" || name === "smartAccount" || name === "accountRead" || name === "subAccount" || name === "credentials" || name === "transport";
|
|
4451
|
+
}
|
|
4452
|
+
function productionBootstrapPortLayer(bootstrap) {
|
|
4453
|
+
return Layer.succeed(BootstrapPortTag, bootstrap);
|
|
4454
|
+
}
|
|
4455
|
+
function productionAuthClientLayer(input, refreshConvexAuthRef) {
|
|
4456
|
+
return (input.runtime === "browser" ? BetterAuthBrowserLayer({
|
|
4457
|
+
authBaseUrl: input.runtimeUrls.authBaseUrl,
|
|
4458
|
+
...input.fetch === void 0 ? {} : { fetch: input.fetch }
|
|
4459
|
+
}) : BetterAuthNodeLayer({
|
|
4460
|
+
authBaseUrl: input.runtimeUrls.authBaseUrl,
|
|
4461
|
+
...input.origin === void 0 ? {} : { origin: input.origin },
|
|
4462
|
+
...input.fetch === void 0 ? {} : { fetch: input.fetch }
|
|
4463
|
+
})).pipe(Layer.map((context) => {
|
|
4464
|
+
const authClient = Context.get(context, AuthClientPortTag);
|
|
4465
|
+
return Context.make(AuthClientPortTag, refreshConvexAuthOnSession(authClient, () => {
|
|
4466
|
+
const refresh = refreshConvexAuthRef.current;
|
|
4467
|
+
if (refresh === null) {
|
|
4468
|
+
refreshConvexAuthRef.pending = true;
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
refresh();
|
|
4472
|
+
}));
|
|
4473
|
+
}));
|
|
4474
|
+
}
|
|
4475
|
+
function productionAuthCacheLayer(input) {
|
|
4476
|
+
return Layer.succeed(AuthCachePortTag, input.authCache ?? detectAuthCacheAdapter());
|
|
4477
|
+
}
|
|
4478
|
+
function productionConvexCallLayer(input, refreshConvexAuthRef) {
|
|
4479
|
+
return Layer.unwrapEffect(Effect.map(AuthClientPortTag, (authClient) => ConvexCallLayer({
|
|
4480
|
+
convexUrl: input.runtimeUrls.convexUrl,
|
|
4481
|
+
...input.convexClient === void 0 ? {} : { client: input.convexClient },
|
|
4482
|
+
tokenProvider: async ({ forceRefreshToken }) => {
|
|
4483
|
+
const tokenResult = await Effect.runPromise(Effect.either(authClient.getConvexJwt({
|
|
4484
|
+
forceRefresh: forceRefreshToken,
|
|
4485
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
4486
|
+
})));
|
|
4487
|
+
if (Either.isLeft(tokenResult)) return null;
|
|
4488
|
+
return String(tokenResult.right.token);
|
|
4489
|
+
}
|
|
4490
|
+
}).pipe(Layer.map((context) => {
|
|
4491
|
+
const convexCall = Context.get(context, ConvexCallPortTag);
|
|
4492
|
+
if (isRefreshableConvexCallPort(convexCall)) {
|
|
4493
|
+
refreshConvexAuthRef.current = () => convexCall.refreshAuth();
|
|
4494
|
+
if (refreshConvexAuthRef.pending) {
|
|
4495
|
+
refreshConvexAuthRef.pending = false;
|
|
4496
|
+
refreshConvexAuthRef.current();
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
return context;
|
|
4500
|
+
}))));
|
|
4501
|
+
}
|
|
4502
|
+
function isRefreshableConvexCallPort(convexCall) {
|
|
4503
|
+
return "refreshAuth" in convexCall && typeof convexCall.refreshAuth === "function";
|
|
4504
|
+
}
|
|
4505
|
+
async function createProductionAdapters(input) {
|
|
4506
|
+
const resolvedInput = resolveInput(input);
|
|
4507
|
+
if (!resolvedInput.ok) return resolvedInput;
|
|
4508
|
+
const scope = await Effect.runPromise(Scope.make());
|
|
4509
|
+
const closeScope = idempotentClose(() => Effect.runPromise(Scope.close(scope, Exit.void)));
|
|
4510
|
+
const bootstrapLayer = HttpBootstrapLayer({
|
|
4511
|
+
bootstrapBaseUrl: resolvedInput.value.bootstrapBaseUrl,
|
|
4512
|
+
...input.fetch === void 0 ? {} : { fetch: input.fetch }
|
|
4513
|
+
});
|
|
4514
|
+
try {
|
|
4515
|
+
const bootstrapContext = await Effect.runPromise(Layer.buildWithScope(bootstrapLayer, scope));
|
|
4516
|
+
const bootstrap = Context.get(bootstrapContext, BootstrapPortTag);
|
|
4517
|
+
const bootstrapResult = await runBootstrap(bootstrap.resolve({
|
|
4518
|
+
publishableKey: resolvedInput.value.publishableKey,
|
|
4519
|
+
...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin }
|
|
4520
|
+
}));
|
|
4521
|
+
if (!bootstrapResult.ok) {
|
|
4522
|
+
await emitBootstrapTelemetry(input.telemetry, {
|
|
4523
|
+
name: "bootstrap_failed",
|
|
4524
|
+
props: {
|
|
4525
|
+
...bootstrapTelemetryEnvelope(input, resolvedInput.value),
|
|
4526
|
+
reason: bootstrapResult.error.code
|
|
4527
|
+
}
|
|
4528
|
+
});
|
|
4529
|
+
await closeScope().catch(() => void 0);
|
|
4530
|
+
return bootstrapResult;
|
|
4531
|
+
}
|
|
4532
|
+
const runtimeUrls = resolveRuntimeUrls(bootstrapResult.value);
|
|
4533
|
+
if (!runtimeUrls.ok) {
|
|
4534
|
+
await emitBootstrapTelemetry(input.telemetry, {
|
|
4535
|
+
name: "bootstrap_failed",
|
|
4536
|
+
props: {
|
|
4537
|
+
...bootstrapTelemetryEnvelope(input, resolvedInput.value),
|
|
4538
|
+
applicationId: bootstrapResult.value.applicationId,
|
|
4539
|
+
reason: runtimeUrls.error.code
|
|
4540
|
+
}
|
|
4541
|
+
});
|
|
4542
|
+
await closeScope().catch(() => void 0);
|
|
4543
|
+
return runtimeUrls;
|
|
4544
|
+
}
|
|
4545
|
+
await emitBootstrapTelemetry(input.telemetry, {
|
|
4546
|
+
name: "bootstrap_resolved",
|
|
4547
|
+
props: {
|
|
4548
|
+
...bootstrapTelemetryEnvelope(input, resolvedInput.value),
|
|
4549
|
+
applicationId: bootstrapResult.value.applicationId
|
|
4550
|
+
}
|
|
4551
|
+
});
|
|
4552
|
+
let injectedClient;
|
|
4553
|
+
const convexClientFactory = input.convexClientFactory;
|
|
4554
|
+
if (convexClientFactory !== void 0) try {
|
|
4555
|
+
injectedClient = convexClientFactory(runtimeUrls.value.convexUrl);
|
|
4556
|
+
} catch (cause) {
|
|
4557
|
+
await closeScope().catch(() => void 0);
|
|
4558
|
+
return {
|
|
4559
|
+
ok: false,
|
|
4560
|
+
error: toPublicError(cause, "createProductionAdapters")
|
|
4561
|
+
};
|
|
4562
|
+
}
|
|
4563
|
+
const portsLayer = mergeProductionAdapterLayers(makeProductionAdapterLayerEntries({
|
|
4564
|
+
bootstrap,
|
|
4565
|
+
runtime: resolvedInput.value.runtime,
|
|
4566
|
+
...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin },
|
|
4567
|
+
runtimeUrls: runtimeUrls.value,
|
|
4568
|
+
chainId: bootstrapResult.value.chainId,
|
|
4569
|
+
...input.authCache === void 0 ? {} : { authCache: input.authCache },
|
|
4570
|
+
...input.telemetry === void 0 ? {} : { telemetry: input.telemetry },
|
|
4571
|
+
...input.evmSigner === void 0 ? {} : { evmSigner: input.evmSigner },
|
|
4572
|
+
...injectedClient === void 0 ? {} : { convexClient: injectedClient },
|
|
4573
|
+
...input.fetch === void 0 ? {} : { fetch: input.fetch },
|
|
4574
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
4575
|
+
}));
|
|
4576
|
+
const portsContext = await Effect.runPromise(Layer.buildWithScope(portsLayer, scope));
|
|
4577
|
+
return {
|
|
4578
|
+
ok: true,
|
|
4579
|
+
value: {
|
|
4580
|
+
ports: await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(portsContext))),
|
|
4581
|
+
bootstrap: bootstrapResult.value,
|
|
4582
|
+
close: closeScope
|
|
4583
|
+
}
|
|
4584
|
+
};
|
|
4585
|
+
} catch (cause) {
|
|
4586
|
+
await closeScope().catch(() => void 0);
|
|
4587
|
+
return {
|
|
4588
|
+
ok: false,
|
|
4589
|
+
error: toPublicError(cause, "createProductionAdapters")
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
function refreshConvexAuthOnSession(authClient, refresh) {
|
|
4594
|
+
return {
|
|
4595
|
+
...authClient,
|
|
4596
|
+
verifyOtp: (input, options) => authClient.verifyOtp(input, options).pipe(Effect.tap(() => Effect.sync(refresh))),
|
|
4597
|
+
signOut: (options) => authClient.signOut(options).pipe(Effect.tap(() => Effect.sync(refresh)))
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
async function createCapxulClient(input) {
|
|
4601
|
+
const adapters = await createProductionAdapters(input);
|
|
4602
|
+
if (!adapters.ok) return adapters;
|
|
4603
|
+
try {
|
|
4604
|
+
const orgDeploymentPort = input.orgDeploymentPortFactory === void 0 ? void 0 : input.orgDeploymentPortFactory(adapters.value.ports.convexCall);
|
|
4605
|
+
const client = assembleCapxulClient({
|
|
4606
|
+
ports: adapters.value.ports,
|
|
4607
|
+
bootstrap: adapters.value.bootstrap,
|
|
4608
|
+
authCache: adapters.value.ports.authCache,
|
|
4609
|
+
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 },
|
|
4617
|
+
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
4618
|
+
...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
|
|
4619
|
+
invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4
|
|
4620
|
+
});
|
|
4621
|
+
return {
|
|
4622
|
+
ok: true,
|
|
4623
|
+
value: {
|
|
4624
|
+
...client,
|
|
4625
|
+
_internal: {
|
|
4626
|
+
...client._internal,
|
|
4627
|
+
close: adapters.value.close
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
};
|
|
4631
|
+
} catch (cause) {
|
|
4632
|
+
await adapters.value.close().catch(() => void 0);
|
|
4633
|
+
return {
|
|
4634
|
+
ok: false,
|
|
4635
|
+
error: toPublicError(cause, "createCapxulClient")
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
function resolveInput(input) {
|
|
4640
|
+
try {
|
|
4641
|
+
const runtime = input.runtime ?? detectRuntime();
|
|
4642
|
+
const publishableKey = toPublishableKey(input.publishableKey);
|
|
4643
|
+
const origin = input.origin === void 0 ? runtime === "browser" ? toAllowedOrigin(derivedBrowserOrigin(runtime)) : void 0 : toAllowedOrigin(input.origin);
|
|
4644
|
+
return {
|
|
4645
|
+
ok: true,
|
|
4646
|
+
value: {
|
|
4647
|
+
publishableKey,
|
|
4648
|
+
...origin === void 0 ? {} : { origin },
|
|
4649
|
+
bootstrapBaseUrl: normalizeHttpUrl("bootstrapBaseUrl", input.bootstrapBaseUrl ?? "https://api.capxul.com"),
|
|
4650
|
+
runtime
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
} catch (cause) {
|
|
4654
|
+
return {
|
|
4655
|
+
ok: false,
|
|
4656
|
+
error: toPublicError(cause, "createProductionAdapters")
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
function detectRuntime() {
|
|
4661
|
+
const globalAny = globalThis;
|
|
4662
|
+
return globalAny.window !== void 0 || globalAny.document !== void 0 ? "browser" : "node";
|
|
4663
|
+
}
|
|
4664
|
+
function derivedBrowserOrigin(runtime) {
|
|
4665
|
+
if (runtime !== "browser") throw Errors.invalidInput("origin", "required outside browser runtime");
|
|
4666
|
+
const globalAny = globalThis;
|
|
4667
|
+
if (typeof globalAny.location?.origin === "string" && globalAny.location.origin.length > 0) return globalAny.location.origin;
|
|
4668
|
+
throw Errors.invalidInput("origin", "required when browser location is unavailable");
|
|
4669
|
+
}
|
|
4670
|
+
function resolveRuntimeUrls(bootstrap) {
|
|
4671
|
+
try {
|
|
4672
|
+
return {
|
|
4673
|
+
ok: true,
|
|
4674
|
+
value: {
|
|
4675
|
+
authBaseUrl: normalizeHttpUrl("authBaseUrl", bootstrap.authBaseUrl),
|
|
4676
|
+
convexUrl: normalizeHttpUrl("convexUrl", bootstrap.convexUrl)
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
} catch (cause) {
|
|
4680
|
+
return {
|
|
4681
|
+
ok: false,
|
|
4682
|
+
error: toPublicError(cause, "createProductionAdapters")
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
async function runBootstrap(effect) {
|
|
4687
|
+
const result = await Effect.runPromise(Effect.either(effect));
|
|
4688
|
+
if (Either.isRight(result)) return {
|
|
4689
|
+
ok: true,
|
|
4690
|
+
value: result.right
|
|
4691
|
+
};
|
|
4692
|
+
return {
|
|
4693
|
+
ok: false,
|
|
4694
|
+
error: toPublicError(result.left, "bootstrap.resolve")
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4697
|
+
function bootstrapTelemetryEnvelope(input, resolvedInput) {
|
|
4698
|
+
return {
|
|
4699
|
+
capxulEnv: resolvedInput.runtime,
|
|
4700
|
+
env: input.requirement ?? "none",
|
|
4701
|
+
...resolvedInput.origin === void 0 ? {} : { origin: resolvedInput.origin },
|
|
4702
|
+
sdkVersion: SDK_VERSION
|
|
4703
|
+
};
|
|
4704
|
+
}
|
|
4705
|
+
async function emitBootstrapTelemetry(telemetry, event) {
|
|
4706
|
+
if (telemetry === void 0) return;
|
|
4707
|
+
await Effect.runPromise(telemetry.emit(event).pipe(Effect.catchAllDefect(() => Effect.void)));
|
|
4708
|
+
}
|
|
4709
|
+
function normalizeHttpUrl(field, raw) {
|
|
4710
|
+
let parsed;
|
|
4711
|
+
try {
|
|
4712
|
+
parsed = new URL(raw);
|
|
4713
|
+
} catch {
|
|
4714
|
+
throw Errors.invalidInput(field, "must be an http or https URL");
|
|
4715
|
+
}
|
|
4716
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
|
|
4717
|
+
return parsed.toString().replace(/\/$/, "");
|
|
4718
|
+
}
|
|
4719
|
+
function toPublicError(cause, operation) {
|
|
4720
|
+
if (cause instanceof CapxulError) return cause;
|
|
4721
|
+
if (typeof cause === "object" && cause !== null) {
|
|
4722
|
+
const publicError = cause.publicError;
|
|
4723
|
+
if (publicError instanceof CapxulError) return publicError;
|
|
4724
|
+
const nestedCause = cause.cause;
|
|
4725
|
+
if (nestedCause instanceof CapxulError) return nestedCause;
|
|
4726
|
+
}
|
|
4727
|
+
return Errors.providerError("sdk-production-adapters", operation, cause);
|
|
4728
|
+
}
|
|
4729
|
+
function idempotentClose(close) {
|
|
4730
|
+
let closed = false;
|
|
4731
|
+
return async () => {
|
|
4732
|
+
if (closed) return;
|
|
4733
|
+
closed = true;
|
|
4734
|
+
await close();
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
//#endregion
|
|
4738
|
+
export { assembleCapxulClient, createCapxulClient, eip1193AccountProvider, injectedWalletSigner, localPrivateKeyAccountProvider, openfortEmbeddedAccountProvider };
|
|
4739
|
+
|
|
4740
|
+
//# sourceMappingURL=index.mjs.map
|