@capxul/sdk 0.1.0-alpha.0 → 0.1.0-alpha.11
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/CHANGELOG.md +136 -0
- package/README.md +87 -5
- package/dist/{client-o4R-ZFh2.d.ts → client-DSxth_GE.d.cts} +467 -117
- package/dist/{client-BNGGubL2.d.cts → client-xqKjPV9I.d.ts} +467 -117
- package/dist/client.cjs +2141 -476
- package/dist/client.d.cts +4 -5
- package/dist/client.d.ts +4 -5
- package/dist/client.js +2141 -476
- package/dist/errors-GgKrSUKp.d.cts +70 -0
- package/dist/errors-QHD5Tlok.d.ts +70 -0
- package/dist/errors.d.cts +2 -55
- package/dist/errors.d.ts +2 -55
- package/dist/index.cjs +2427 -764
- package/dist/index.d.cts +16 -11
- package/dist/index.d.ts +16 -11
- package/dist/index.js +2425 -765
- package/dist/next-action-DkrwXYay.d.cts +177 -0
- package/dist/next-action-DkrwXYay.d.ts +177 -0
- package/dist/types-PM4AQRLP.d.ts +1136 -0
- package/dist/types-hfcOE7Oi.d.cts +1136 -0
- package/dist/webhooks.d.cts +2 -3
- package/dist/webhooks.d.ts +2 -3
- package/package.json +12 -11
- package/dist/types-Cokyqgwm.d.cts +0 -202
- package/dist/types-Cokyqgwm.d.ts +0 -202
package/dist/client.js
CHANGED
|
@@ -67,12 +67,681 @@ function fromConvexError(error) {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// ../observability/src/try-catch.ts
|
|
71
|
+
async function tryCatch(promise) {
|
|
72
|
+
try {
|
|
73
|
+
return [null, await promise];
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return [e instanceof Error ? e : new Error(String(e)), null];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ../observability/src/debug-log.ts
|
|
80
|
+
function isDevelopmentBuild() {
|
|
81
|
+
if (typeof process === "undefined") {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
|
|
85
|
+
}
|
|
86
|
+
function debugLog(line) {
|
|
87
|
+
if (!isDevelopmentBuild()) return;
|
|
88
|
+
if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
|
|
89
|
+
console.info(line);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
|
|
93
|
+
process.stderr.write(`${line}
|
|
94
|
+
`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function formatDebugValue(value) {
|
|
98
|
+
if (value === void 0 || value === "") return "";
|
|
99
|
+
if (typeof value === "string") return value;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.stringify(value);
|
|
102
|
+
} catch {
|
|
103
|
+
return String(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function track(...args) {
|
|
107
|
+
const [name, props] = args;
|
|
108
|
+
debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
|
|
109
|
+
}
|
|
110
|
+
function formatDebugValue2(value) {
|
|
111
|
+
if (value === void 0 || value === "") return "";
|
|
112
|
+
if (typeof value === "string") return value;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
} catch {
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function identify(userId, traits) {
|
|
120
|
+
debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ../platform-kernel/src/ids.ts
|
|
124
|
+
function makePrefixedIdConstructor(prefix, fieldName) {
|
|
125
|
+
const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
|
|
126
|
+
return (raw) => {
|
|
127
|
+
if (typeof raw !== "string" || !re.test(raw)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return raw;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
var toOperationId = makePrefixedIdConstructor(
|
|
136
|
+
"op",
|
|
137
|
+
"operationId"
|
|
138
|
+
);
|
|
139
|
+
var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
|
|
140
|
+
var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
|
|
141
|
+
var toSubAccountId = makePrefixedIdConstructor(
|
|
142
|
+
"sub",
|
|
143
|
+
"subAccountId"
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// src/core/external-accounts.ts
|
|
147
|
+
function brandExternalAccount(raw) {
|
|
148
|
+
return {
|
|
149
|
+
...raw,
|
|
150
|
+
id: toExternalAccountId(raw.id),
|
|
151
|
+
operation: {
|
|
152
|
+
id: toOperationId(raw.operation.id),
|
|
153
|
+
status: raw.operation.status,
|
|
154
|
+
correlationId: toCorrelationId(raw.operation.correlationId)
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function createExternalAccountsClient(config = {}) {
|
|
159
|
+
return {
|
|
160
|
+
retrieve: async (externalAccountId) => {
|
|
161
|
+
if (!config.data) {
|
|
162
|
+
return stub(
|
|
163
|
+
"externalAccounts.retrieve"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const [err, raw] = await tryCatch(
|
|
167
|
+
config.data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
168
|
+
externalAccountId
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
if (err) {
|
|
172
|
+
return [
|
|
173
|
+
fromConvexError(err),
|
|
174
|
+
null
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
if (!raw) {
|
|
178
|
+
return [
|
|
179
|
+
new CapxulError({
|
|
180
|
+
code: "NOT_FOUND",
|
|
181
|
+
message: `external_account ${externalAccountId} not found`
|
|
182
|
+
}),
|
|
183
|
+
null
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [null, brandExternalAccount(raw)];
|
|
187
|
+
},
|
|
188
|
+
remove: async (externalAccountId) => {
|
|
189
|
+
if (!config.data) {
|
|
190
|
+
return stub("externalAccounts.remove");
|
|
191
|
+
}
|
|
192
|
+
const [err] = await tryCatch(
|
|
193
|
+
config.data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
194
|
+
externalAccountId
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
if (err) {
|
|
198
|
+
return [
|
|
199
|
+
fromConvexError(err),
|
|
200
|
+
null
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
return [null, void 0];
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/core/sub-accounts.ts
|
|
209
|
+
function malformedWireError(reason, raw) {
|
|
210
|
+
return new CapxulError({
|
|
211
|
+
code: "PROVIDER_ERROR",
|
|
212
|
+
message: `convex brandSubAccount failed: ${reason}`,
|
|
213
|
+
details: {
|
|
214
|
+
provider: "convex",
|
|
215
|
+
operation: "brandSubAccount",
|
|
216
|
+
reason,
|
|
217
|
+
// PII discipline (`.claude/rules/posthog.md`): user-authored
|
|
218
|
+
// strings on the wire (`name`, `purpose`) are customer-confidential
|
|
219
|
+
// — sub-account names like "Q3 Acquisition Reserve" or
|
|
220
|
+
// "Vendor ABC payments" must not flow into telemetry. We emit a
|
|
221
|
+
// structural keys-only sample via a strict ALLOWLIST so any future
|
|
222
|
+
// wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
|
|
223
|
+
// by construction rather than leaked through a denylist gap.
|
|
224
|
+
sample: safeSampleShape(raw)
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function safeSampleShape(raw) {
|
|
229
|
+
if (raw === null || typeof raw !== "object") {
|
|
230
|
+
return { type: typeof raw };
|
|
231
|
+
}
|
|
232
|
+
const r = raw;
|
|
233
|
+
const balance = r.balance;
|
|
234
|
+
return {
|
|
235
|
+
object: typeof r.object === "string" ? r.object : typeof r.object,
|
|
236
|
+
idPresent: typeof r.id === "string" && r.id.length > 0,
|
|
237
|
+
// First 4 chars only — enough to distinguish "sub_" prefixed IDs
|
|
238
|
+
// from accidental other resource IDs without leaking the full ID.
|
|
239
|
+
idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
|
|
240
|
+
parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
|
|
241
|
+
status: r.status,
|
|
242
|
+
hasName: typeof r.name === "string",
|
|
243
|
+
hasPurpose: r.purpose !== void 0,
|
|
244
|
+
balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
|
|
245
|
+
createdAtType: typeof r.createdAt,
|
|
246
|
+
updatedAtType: typeof r.updatedAt
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function isMoneyShape(v) {
|
|
250
|
+
if (typeof v !== "object" || v === null) return false;
|
|
251
|
+
const m = v;
|
|
252
|
+
return typeof m.value === "string" && typeof m.currency === "string";
|
|
253
|
+
}
|
|
254
|
+
function isParentShape(v) {
|
|
255
|
+
if (typeof v !== "object" || v === null) return false;
|
|
256
|
+
const p = v;
|
|
257
|
+
return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
|
|
258
|
+
}
|
|
259
|
+
function isFiniteNonNegativeInteger(v) {
|
|
260
|
+
return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
|
|
261
|
+
}
|
|
262
|
+
function validateWireSubAccount(raw) {
|
|
263
|
+
if (typeof raw !== "object" || raw === null) {
|
|
264
|
+
return { ok: false, reason: "not an object" };
|
|
265
|
+
}
|
|
266
|
+
const r = raw;
|
|
267
|
+
if (r.object !== "sub_account") {
|
|
268
|
+
return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
|
|
269
|
+
}
|
|
270
|
+
if (typeof r.id !== "string" || r.id.length === 0) {
|
|
271
|
+
return { ok: false, reason: "id must be a non-empty string" };
|
|
272
|
+
}
|
|
273
|
+
if (!isParentShape(r.parent)) {
|
|
274
|
+
return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
|
|
275
|
+
}
|
|
276
|
+
if (typeof r.name !== "string") {
|
|
277
|
+
return { ok: false, reason: "name must be a string" };
|
|
278
|
+
}
|
|
279
|
+
if (r.purpose !== void 0 && typeof r.purpose !== "string") {
|
|
280
|
+
return { ok: false, reason: "purpose must be a string when present" };
|
|
281
|
+
}
|
|
282
|
+
if (r.status !== "active" && r.status !== "archived") {
|
|
283
|
+
return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
|
|
284
|
+
}
|
|
285
|
+
if (!isMoneyShape(r.balance)) {
|
|
286
|
+
return { ok: false, reason: "balance must be { value: string, currency: string }" };
|
|
287
|
+
}
|
|
288
|
+
if (!isFiniteNonNegativeInteger(r.createdAt)) {
|
|
289
|
+
return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
|
|
290
|
+
}
|
|
291
|
+
if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
|
|
292
|
+
return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
|
|
293
|
+
}
|
|
294
|
+
return { ok: true, value: r };
|
|
295
|
+
}
|
|
296
|
+
function brandSubAccount(raw) {
|
|
297
|
+
const result = validateWireSubAccount(raw);
|
|
298
|
+
if (!result.ok) {
|
|
299
|
+
throw malformedWireError(result.reason, raw);
|
|
300
|
+
}
|
|
301
|
+
const wire = result.value;
|
|
302
|
+
return {
|
|
303
|
+
object: wire.object,
|
|
304
|
+
id: toSubAccountId(wire.id),
|
|
305
|
+
parent: wire.parent,
|
|
306
|
+
name: wire.name,
|
|
307
|
+
...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
|
|
308
|
+
status: wire.status,
|
|
309
|
+
balance: wire.balance,
|
|
310
|
+
createdAt: new Date(wire.createdAt).toISOString()
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function tryBrandSubAccount(raw) {
|
|
314
|
+
try {
|
|
315
|
+
return [null, brandSubAccount(raw)];
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
|
|
318
|
+
return [err, null];
|
|
319
|
+
}
|
|
320
|
+
return [
|
|
321
|
+
malformedWireError(
|
|
322
|
+
err instanceof Error ? err.message : String(err),
|
|
323
|
+
raw
|
|
324
|
+
),
|
|
325
|
+
null
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function createSubAccountsClient(config = {}) {
|
|
330
|
+
return {
|
|
331
|
+
retrieve: async (subAccountId) => {
|
|
332
|
+
if (!config.data) {
|
|
333
|
+
return stub("subAccounts.retrieve");
|
|
334
|
+
}
|
|
335
|
+
const [err, raw] = await tryCatch(
|
|
336
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
337
|
+
subAccountId
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
if (err) {
|
|
341
|
+
return [
|
|
342
|
+
fromConvexError(err),
|
|
343
|
+
null
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
if (!raw) {
|
|
347
|
+
return [
|
|
348
|
+
new CapxulError({
|
|
349
|
+
code: "NOT_FOUND",
|
|
350
|
+
message: `sub_account ${subAccountId} not found`
|
|
351
|
+
}),
|
|
352
|
+
null
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
356
|
+
if (brandErr) {
|
|
357
|
+
return [brandErr, null];
|
|
358
|
+
}
|
|
359
|
+
return [null, branded];
|
|
360
|
+
},
|
|
361
|
+
remove: async (subAccountId) => {
|
|
362
|
+
if (!config.data) {
|
|
363
|
+
return stub("subAccounts.remove");
|
|
364
|
+
}
|
|
365
|
+
const [err, raw] = await tryCatch(
|
|
366
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
367
|
+
subAccountId
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
if (err) {
|
|
371
|
+
return [
|
|
372
|
+
fromConvexError(err),
|
|
373
|
+
null
|
|
374
|
+
];
|
|
375
|
+
}
|
|
376
|
+
if (!raw) {
|
|
377
|
+
return [
|
|
378
|
+
new CapxulError({
|
|
379
|
+
code: "NOT_FOUND",
|
|
380
|
+
message: `sub_account ${subAccountId} not found`
|
|
381
|
+
}),
|
|
382
|
+
null
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
386
|
+
if (brandErr) {
|
|
387
|
+
return [brandErr, null];
|
|
388
|
+
}
|
|
389
|
+
return [null, branded];
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
70
394
|
// src/core/accounts.ts
|
|
395
|
+
function createAccountExternalAccountsClient(config) {
|
|
396
|
+
return {
|
|
397
|
+
create: async (input) => {
|
|
398
|
+
if (!config.data) {
|
|
399
|
+
return stub(
|
|
400
|
+
"accounts.externalAccounts.create"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const [err, raw] = await tryCatch(
|
|
404
|
+
config.data.mutation(
|
|
405
|
+
api.externalAccounts.mutations.createPersonal,
|
|
406
|
+
{
|
|
407
|
+
kind: input.kind,
|
|
408
|
+
label: input.label,
|
|
409
|
+
address: input.address,
|
|
410
|
+
iban: input.iban,
|
|
411
|
+
bic: input.bic,
|
|
412
|
+
accountHolder: input.accountHolder,
|
|
413
|
+
network: input.network,
|
|
414
|
+
panToken: input.panToken,
|
|
415
|
+
last4: input.last4
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
);
|
|
419
|
+
if (err) {
|
|
420
|
+
return [
|
|
421
|
+
fromConvexError(err),
|
|
422
|
+
null
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
if (!raw) {
|
|
426
|
+
return [
|
|
427
|
+
new CapxulError({
|
|
428
|
+
code: "NOT_FOUND",
|
|
429
|
+
message: "external_account creation returned no resource"
|
|
430
|
+
}),
|
|
431
|
+
null
|
|
432
|
+
];
|
|
433
|
+
}
|
|
434
|
+
return [
|
|
435
|
+
null,
|
|
436
|
+
brandExternalAccount(
|
|
437
|
+
raw
|
|
438
|
+
)
|
|
439
|
+
];
|
|
440
|
+
},
|
|
441
|
+
list: async (input) => {
|
|
442
|
+
if (!config.data) {
|
|
443
|
+
return stub(
|
|
444
|
+
"accounts.externalAccounts.list"
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const [err, result] = await tryCatch(
|
|
448
|
+
config.data.query(api.externalAccounts.queries.listPersonal, {
|
|
449
|
+
limit: input.limit,
|
|
450
|
+
cursor: input.cursor
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
if (err) {
|
|
454
|
+
return [fromConvexError(err), null];
|
|
455
|
+
}
|
|
456
|
+
const branded = result.data.map(
|
|
457
|
+
(row) => brandExternalAccount(
|
|
458
|
+
row
|
|
459
|
+
)
|
|
460
|
+
);
|
|
461
|
+
return [
|
|
462
|
+
null,
|
|
463
|
+
{
|
|
464
|
+
object: "list",
|
|
465
|
+
data: branded,
|
|
466
|
+
page: result.page
|
|
467
|
+
}
|
|
468
|
+
];
|
|
469
|
+
},
|
|
470
|
+
retrieve: async (externalAccountId) => {
|
|
471
|
+
if (!config.data) {
|
|
472
|
+
return stub(
|
|
473
|
+
"accounts.externalAccounts.retrieve"
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const [err, raw] = await tryCatch(
|
|
477
|
+
config.data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
478
|
+
externalAccountId
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
if (err) {
|
|
482
|
+
return [
|
|
483
|
+
fromConvexError(err),
|
|
484
|
+
null
|
|
485
|
+
];
|
|
486
|
+
}
|
|
487
|
+
if (!raw) {
|
|
488
|
+
return [
|
|
489
|
+
new CapxulError({
|
|
490
|
+
code: "NOT_FOUND",
|
|
491
|
+
message: `external_account ${externalAccountId} not found`
|
|
492
|
+
}),
|
|
493
|
+
null
|
|
494
|
+
];
|
|
495
|
+
}
|
|
496
|
+
return [
|
|
497
|
+
null,
|
|
498
|
+
brandExternalAccount(
|
|
499
|
+
raw
|
|
500
|
+
)
|
|
501
|
+
];
|
|
502
|
+
},
|
|
503
|
+
remove: async (externalAccountId) => {
|
|
504
|
+
if (!config.data) {
|
|
505
|
+
return stub("accounts.externalAccounts.remove");
|
|
506
|
+
}
|
|
507
|
+
const [err] = await tryCatch(
|
|
508
|
+
config.data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
509
|
+
externalAccountId
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
if (err) {
|
|
513
|
+
return [
|
|
514
|
+
fromConvexError(err),
|
|
515
|
+
null
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
return [null, void 0];
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function createAccountSubAccountsClient(config) {
|
|
523
|
+
return {
|
|
524
|
+
create: async (input) => {
|
|
525
|
+
if (!config.data) {
|
|
526
|
+
return stub(
|
|
527
|
+
"accounts.subAccounts.create"
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const [err, raw] = await tryCatch(
|
|
531
|
+
config.data.mutation(api.subAccounts.mutations.create, {
|
|
532
|
+
parent: { kind: "account", id: input.accountId },
|
|
533
|
+
name: input.name,
|
|
534
|
+
purpose: input.purpose
|
|
535
|
+
})
|
|
536
|
+
);
|
|
537
|
+
if (err) {
|
|
538
|
+
return [
|
|
539
|
+
fromConvexError(err),
|
|
540
|
+
null
|
|
541
|
+
];
|
|
542
|
+
}
|
|
543
|
+
if (!raw) {
|
|
544
|
+
return [
|
|
545
|
+
new CapxulError({
|
|
546
|
+
code: "NOT_FOUND",
|
|
547
|
+
message: "sub_account creation returned no resource"
|
|
548
|
+
}),
|
|
549
|
+
null
|
|
550
|
+
];
|
|
551
|
+
}
|
|
552
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
553
|
+
if (brandErr) {
|
|
554
|
+
return [
|
|
555
|
+
brandErr,
|
|
556
|
+
null
|
|
557
|
+
];
|
|
558
|
+
}
|
|
559
|
+
return [null, branded];
|
|
560
|
+
},
|
|
561
|
+
list: async (input) => {
|
|
562
|
+
if (!config.data) {
|
|
563
|
+
return stub(
|
|
564
|
+
"accounts.subAccounts.list"
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const [err, rows] = await tryCatch(
|
|
568
|
+
config.data.query(api.subAccounts.queries.listByAccount, {
|
|
569
|
+
accountId: input.accountId
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
if (err) {
|
|
573
|
+
return [
|
|
574
|
+
fromConvexError(err),
|
|
575
|
+
null
|
|
576
|
+
];
|
|
577
|
+
}
|
|
578
|
+
const branded = [];
|
|
579
|
+
for (const row of rows) {
|
|
580
|
+
const [brandErr, value] = tryBrandSubAccount(row);
|
|
581
|
+
if (brandErr) {
|
|
582
|
+
return [
|
|
583
|
+
brandErr,
|
|
584
|
+
null
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
branded.push(value);
|
|
588
|
+
}
|
|
589
|
+
return [
|
|
590
|
+
null,
|
|
591
|
+
{
|
|
592
|
+
object: "list",
|
|
593
|
+
data: branded,
|
|
594
|
+
page: { hasMore: false }
|
|
595
|
+
}
|
|
596
|
+
];
|
|
597
|
+
},
|
|
598
|
+
retrieve: async (subAccountId) => {
|
|
599
|
+
if (!config.data) {
|
|
600
|
+
return stub(
|
|
601
|
+
"accounts.subAccounts.retrieve"
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const [err, raw] = await tryCatch(
|
|
605
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
606
|
+
subAccountId
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
if (err) {
|
|
610
|
+
return [
|
|
611
|
+
fromConvexError(err),
|
|
612
|
+
null
|
|
613
|
+
];
|
|
614
|
+
}
|
|
615
|
+
if (!raw) {
|
|
616
|
+
return [
|
|
617
|
+
new CapxulError({
|
|
618
|
+
code: "NOT_FOUND",
|
|
619
|
+
message: `sub_account ${subAccountId} not found`
|
|
620
|
+
}),
|
|
621
|
+
null
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
625
|
+
if (brandErr) {
|
|
626
|
+
return [
|
|
627
|
+
brandErr,
|
|
628
|
+
null
|
|
629
|
+
];
|
|
630
|
+
}
|
|
631
|
+
return [null, branded];
|
|
632
|
+
},
|
|
633
|
+
remove: async (subAccountId) => {
|
|
634
|
+
if (!config.data) {
|
|
635
|
+
return stub(
|
|
636
|
+
"accounts.subAccounts.remove"
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
const [err, raw] = await tryCatch(
|
|
640
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
641
|
+
subAccountId
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
if (err) {
|
|
645
|
+
return [
|
|
646
|
+
fromConvexError(err),
|
|
647
|
+
null
|
|
648
|
+
];
|
|
649
|
+
}
|
|
650
|
+
if (!raw) {
|
|
651
|
+
return [
|
|
652
|
+
new CapxulError({
|
|
653
|
+
code: "NOT_FOUND",
|
|
654
|
+
message: `sub_account ${subAccountId} not found`
|
|
655
|
+
}),
|
|
656
|
+
null
|
|
657
|
+
];
|
|
658
|
+
}
|
|
659
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
660
|
+
if (brandErr) {
|
|
661
|
+
return [
|
|
662
|
+
brandErr,
|
|
663
|
+
null
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
return [null, branded];
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
71
670
|
function createAccountsClient(config = {}) {
|
|
72
671
|
return {
|
|
73
|
-
retrieve: async () =>
|
|
672
|
+
retrieve: async (accountId) => {
|
|
673
|
+
if (!config.data) {
|
|
674
|
+
return stub("accounts.retrieve");
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
const account = await config.data.query(
|
|
678
|
+
api.openfort.queries.getMyAccount,
|
|
679
|
+
{}
|
|
680
|
+
);
|
|
681
|
+
if (account.id !== accountId) {
|
|
682
|
+
return [
|
|
683
|
+
new CapxulError({
|
|
684
|
+
code: "PERMISSION_DENIED",
|
|
685
|
+
message: "accounts.retrieve currently supports the authenticated caller's own account only.",
|
|
686
|
+
details: {
|
|
687
|
+
requestedAccountId: accountId,
|
|
688
|
+
authenticatedAccountId: account.id
|
|
689
|
+
}
|
|
690
|
+
}),
|
|
691
|
+
null
|
|
692
|
+
];
|
|
693
|
+
}
|
|
694
|
+
return [null, account];
|
|
695
|
+
} catch (cause) {
|
|
696
|
+
return [fromConvexError(cause), null];
|
|
697
|
+
}
|
|
698
|
+
},
|
|
74
699
|
lookup: async () => stub("accounts.lookup"),
|
|
75
|
-
update: async () =>
|
|
700
|
+
update: async (input) => {
|
|
701
|
+
if (!config.data) {
|
|
702
|
+
return stub("accounts.update");
|
|
703
|
+
}
|
|
704
|
+
if (input.countryCode !== void 0) {
|
|
705
|
+
return [
|
|
706
|
+
new CapxulError({
|
|
707
|
+
code: "INVALID_INPUT",
|
|
708
|
+
message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
709
|
+
details: { field: "countryCode" }
|
|
710
|
+
}),
|
|
711
|
+
null
|
|
712
|
+
];
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
const current = await config.data.query(
|
|
716
|
+
api.openfort.queries.getMyAccount,
|
|
717
|
+
{}
|
|
718
|
+
);
|
|
719
|
+
if (current.id !== input.accountId) {
|
|
720
|
+
return [
|
|
721
|
+
new CapxulError({
|
|
722
|
+
code: "PERMISSION_DENIED",
|
|
723
|
+
message: "accounts.update currently supports the authenticated caller's own account only.",
|
|
724
|
+
details: {
|
|
725
|
+
requestedAccountId: input.accountId,
|
|
726
|
+
authenticatedAccountId: current.id
|
|
727
|
+
}
|
|
728
|
+
}),
|
|
729
|
+
null
|
|
730
|
+
];
|
|
731
|
+
}
|
|
732
|
+
await config.data.mutation(api.openfort.mutations.updateProfile, {
|
|
733
|
+
displayName: input.name,
|
|
734
|
+
username: input.username
|
|
735
|
+
});
|
|
736
|
+
const updated = await config.data.query(
|
|
737
|
+
api.openfort.queries.getMyAccount,
|
|
738
|
+
{}
|
|
739
|
+
);
|
|
740
|
+
return [null, updated];
|
|
741
|
+
} catch (cause) {
|
|
742
|
+
return [fromConvexError(cause), null];
|
|
743
|
+
}
|
|
744
|
+
},
|
|
76
745
|
provisionPersonal: async (input) => {
|
|
77
746
|
if (!config.data) {
|
|
78
747
|
return stub(
|
|
@@ -152,24 +821,8 @@ function createAccountsClient(config = {}) {
|
|
|
152
821
|
create: async () => stub("accounts.kycProfiles.create"),
|
|
153
822
|
retrieve: async () => stub("accounts.kycProfiles.retrieve")
|
|
154
823
|
},
|
|
155
|
-
externalAccounts:
|
|
156
|
-
|
|
157
|
-
"accounts.externalAccounts.create"
|
|
158
|
-
),
|
|
159
|
-
list: async () => stub(
|
|
160
|
-
"accounts.externalAccounts.list"
|
|
161
|
-
),
|
|
162
|
-
retrieve: async () => stub(
|
|
163
|
-
"accounts.externalAccounts.retrieve"
|
|
164
|
-
),
|
|
165
|
-
remove: async () => stub("accounts.externalAccounts.remove")
|
|
166
|
-
},
|
|
167
|
-
subAccounts: {
|
|
168
|
-
create: async () => stub("accounts.subAccounts.create"),
|
|
169
|
-
list: async () => stub("accounts.subAccounts.list"),
|
|
170
|
-
retrieve: async () => stub("accounts.subAccounts.retrieve"),
|
|
171
|
-
remove: async () => stub("accounts.subAccounts.remove")
|
|
172
|
-
},
|
|
824
|
+
externalAccounts: createAccountExternalAccountsClient(config),
|
|
825
|
+
subAccounts: createAccountSubAccountsClient(config),
|
|
173
826
|
balanceLedger: {
|
|
174
827
|
list: async () => stub(
|
|
175
828
|
"accounts.balanceLedger.list"
|
|
@@ -228,10 +881,16 @@ var Errors = {
|
|
|
228
881
|
`Shield API error (${status}): ${detail}`,
|
|
229
882
|
{ details: { provider: "shield", status } }
|
|
230
883
|
),
|
|
231
|
-
providerError: (provider, operation, cause) =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
884
|
+
providerError: (provider, operation, cause) => (
|
|
885
|
+
// Public `message` is redacted to a fixed shape so provider-side
|
|
886
|
+
// exception text never leaks to the client. The original `cause`
|
|
887
|
+
// is preserved on `Error.cause` for server-side debugging via
|
|
888
|
+
// observability sinks (Sentry, console traces).
|
|
889
|
+
new CapxulError2(
|
|
890
|
+
"PROVIDER_ERROR",
|
|
891
|
+
`Provider error: ${provider} ${operation}`,
|
|
892
|
+
{ cause, details: { provider, operation } }
|
|
893
|
+
)
|
|
235
894
|
),
|
|
236
895
|
invalidInput: (field, reason) => new CapxulError2(
|
|
237
896
|
"INVALID_INPUT",
|
|
@@ -257,8 +916,35 @@ var Errors = {
|
|
|
257
916
|
"Idempotency key was already used for a different request",
|
|
258
917
|
{ details }
|
|
259
918
|
),
|
|
260
|
-
emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}
|
|
261
|
-
|
|
919
|
+
emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
|
|
920
|
+
details
|
|
921
|
+
}),
|
|
922
|
+
rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
|
|
923
|
+
details: { ...details }
|
|
924
|
+
}),
|
|
925
|
+
internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
926
|
+
/**
|
|
927
|
+
* Verification gate. Surfaced when a request hits a verification
|
|
928
|
+
* boundary the actor cannot cross under their current state. Two
|
|
929
|
+
* variants share this code:
|
|
930
|
+
*
|
|
931
|
+
* - Rail gate (Withdrawals v1 W2, #465): the resolved
|
|
932
|
+
* `external_account.kind` routes to a withdrawal rail (e.g.
|
|
933
|
+
* `fiat_offramp`, `card_payout`) that is not yet supported. Carries
|
|
934
|
+
* `details.rail` + `details.currentKind`.
|
|
935
|
+
* - KYC tier gate (legacy / future): the actor's KYC tier is below
|
|
936
|
+
* the required tier. Carries `details.requiredTier`.
|
|
937
|
+
*
|
|
938
|
+
* Code is shared because both expose the same UX shape ("you cannot
|
|
939
|
+
* proceed until verification advances"); the `details.*` keys
|
|
940
|
+
* differentiate the route.
|
|
941
|
+
*/
|
|
942
|
+
verificationRequired: (details) => {
|
|
943
|
+
const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
|
|
944
|
+
return new CapxulError2("VERIFICATION_REQUIRED", message, {
|
|
945
|
+
details: { ...details }
|
|
946
|
+
});
|
|
947
|
+
}
|
|
262
948
|
};
|
|
263
949
|
|
|
264
950
|
// ../config/src/safe.ts
|
|
@@ -308,13 +994,13 @@ function createLifecycle(initial) {
|
|
|
308
994
|
}
|
|
309
995
|
function makeBuildTimeUrlsTransport(config) {
|
|
310
996
|
if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
|
|
311
|
-
throw
|
|
997
|
+
throw invalidConfigError(
|
|
312
998
|
"authBaseUrl",
|
|
313
999
|
"build-time-urls transport requires a non-empty authBaseUrl."
|
|
314
1000
|
);
|
|
315
1001
|
}
|
|
316
1002
|
if (!config.convexUrl || config.convexUrl.trim().length === 0) {
|
|
317
|
-
throw
|
|
1003
|
+
throw invalidConfigError(
|
|
318
1004
|
"convexUrl",
|
|
319
1005
|
"build-time-urls transport requires a non-empty convexUrl."
|
|
320
1006
|
);
|
|
@@ -328,6 +1014,7 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
328
1014
|
return {
|
|
329
1015
|
authBaseUrl,
|
|
330
1016
|
convexUrl,
|
|
1017
|
+
ensureRuntime: async () => runtime,
|
|
331
1018
|
fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
|
|
332
1019
|
getState: lifecycle.getState,
|
|
333
1020
|
subscribe: lifecycle.subscribe,
|
|
@@ -343,14 +1030,15 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
343
1030
|
};
|
|
344
1031
|
}
|
|
345
1032
|
function makePublishableKeyTransport(config) {
|
|
346
|
-
|
|
347
|
-
|
|
1033
|
+
const publishableKey = config.publishableKey?.trim();
|
|
1034
|
+
if (!publishableKey) {
|
|
1035
|
+
throw invalidConfigError(
|
|
348
1036
|
"publishableKey",
|
|
349
1037
|
"publishable-key transport requires a non-empty publishableKey."
|
|
350
1038
|
);
|
|
351
1039
|
}
|
|
352
1040
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
353
|
-
const bootstrapUrl =
|
|
1041
|
+
const bootstrapUrl = normalizeBootstrapUrl(
|
|
354
1042
|
config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
|
|
355
1043
|
);
|
|
356
1044
|
let authBaseUrl = "";
|
|
@@ -365,23 +1053,20 @@ function makePublishableKeyTransport(config) {
|
|
|
365
1053
|
const response = await fetchImpl(bootstrapUrl, {
|
|
366
1054
|
method: "POST",
|
|
367
1055
|
headers: { "content-type": "application/json" },
|
|
368
|
-
body: JSON.stringify({ publishableKey
|
|
1056
|
+
body: JSON.stringify({ publishableKey })
|
|
369
1057
|
});
|
|
370
1058
|
if (!response.ok) {
|
|
371
|
-
throw
|
|
372
|
-
"publishableKey",
|
|
373
|
-
`${bootstrapUrl} failed with HTTP ${response.status}.`
|
|
374
|
-
);
|
|
1059
|
+
throw await bootstrapResponseError(response, bootstrapUrl);
|
|
375
1060
|
}
|
|
376
|
-
const body = await response
|
|
1061
|
+
const body = await readBootstrapSuccessBody(response);
|
|
377
1062
|
if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
|
|
378
|
-
throw
|
|
1063
|
+
throw bootstrapContractError(
|
|
379
1064
|
"authBaseUrl",
|
|
380
1065
|
"/v1/client/bootstrap returned no authBaseUrl."
|
|
381
1066
|
);
|
|
382
1067
|
}
|
|
383
1068
|
if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
|
|
384
|
-
throw
|
|
1069
|
+
throw bootstrapContractError(
|
|
385
1070
|
"convexUrl",
|
|
386
1071
|
"/v1/client/bootstrap returned no convexUrl."
|
|
387
1072
|
);
|
|
@@ -393,16 +1078,13 @@ function makePublishableKeyTransport(config) {
|
|
|
393
1078
|
return runtime;
|
|
394
1079
|
})();
|
|
395
1080
|
bootstrapPromise = attempt.catch((err) => {
|
|
1081
|
+
const error = normalizeBootstrapThrownError(err);
|
|
396
1082
|
bootstrapPromise = null;
|
|
397
1083
|
lifecycle.setState({
|
|
398
1084
|
status: "error",
|
|
399
|
-
error
|
|
400
|
-
code: "UNKNOWN",
|
|
401
|
-
message: "Bootstrap failed without a typed CapxulError.",
|
|
402
|
-
cause: err
|
|
403
|
-
})
|
|
1085
|
+
error
|
|
404
1086
|
});
|
|
405
|
-
throw
|
|
1087
|
+
throw error;
|
|
406
1088
|
});
|
|
407
1089
|
return await bootstrapPromise;
|
|
408
1090
|
}
|
|
@@ -413,6 +1095,7 @@ function makePublishableKeyTransport(config) {
|
|
|
413
1095
|
get convexUrl() {
|
|
414
1096
|
return convexUrl;
|
|
415
1097
|
},
|
|
1098
|
+
ensureRuntime: ensureBootstrap,
|
|
416
1099
|
fetch: async (path, init) => {
|
|
417
1100
|
const resolved = await ensureBootstrap();
|
|
418
1101
|
return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
|
|
@@ -423,7 +1106,7 @@ function makePublishableKeyTransport(config) {
|
|
|
423
1106
|
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
424
1107
|
const current = lifecycle.getState();
|
|
425
1108
|
if (current.status !== "ready" && current.status !== "authenticated") {
|
|
426
|
-
throw
|
|
1109
|
+
throw internalTransportError(
|
|
427
1110
|
`markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
|
|
428
1111
|
);
|
|
429
1112
|
}
|
|
@@ -445,6 +1128,24 @@ function makePublishableKeyTransport(config) {
|
|
|
445
1128
|
function stripTrailingSlash(url) {
|
|
446
1129
|
return url.replace(/\/+$/, "");
|
|
447
1130
|
}
|
|
1131
|
+
function normalizeBootstrapUrl(url) {
|
|
1132
|
+
const normalized = stripTrailingSlash(url.trim());
|
|
1133
|
+
if (!isAbsoluteHttpUrl(normalized)) {
|
|
1134
|
+
throw invalidConfigError(
|
|
1135
|
+
"bootstrapUrl",
|
|
1136
|
+
"publishable-key transport requires an absolute http(s) bootstrapUrl."
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
return normalized;
|
|
1140
|
+
}
|
|
1141
|
+
function isAbsoluteHttpUrl(url) {
|
|
1142
|
+
try {
|
|
1143
|
+
const parsed = new URL(url);
|
|
1144
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
1145
|
+
} catch {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
448
1149
|
function resolveUrl(authBaseUrl, path) {
|
|
449
1150
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
450
1151
|
return path;
|
|
@@ -452,10 +1153,142 @@ function resolveUrl(authBaseUrl, path) {
|
|
|
452
1153
|
return `${authBaseUrl}${path}`;
|
|
453
1154
|
}
|
|
454
1155
|
function assertNever(value) {
|
|
455
|
-
throw
|
|
1156
|
+
throw internalTransportError(
|
|
456
1157
|
`Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
|
|
457
1158
|
);
|
|
458
1159
|
}
|
|
1160
|
+
function invalidConfigError(field, reason) {
|
|
1161
|
+
return new CapxulError({
|
|
1162
|
+
code: "INVALID_INPUT",
|
|
1163
|
+
message: `Invalid ${field}: ${reason}`,
|
|
1164
|
+
details: { source: "sdk-config", field, reason }
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function bootstrapContractError(field, message) {
|
|
1168
|
+
return new CapxulError({
|
|
1169
|
+
code: "INVALID_INPUT",
|
|
1170
|
+
message,
|
|
1171
|
+
details: {
|
|
1172
|
+
source: "backend-bootstrap",
|
|
1173
|
+
phase: "publishable-key-bootstrap",
|
|
1174
|
+
field,
|
|
1175
|
+
reason: message
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
function internalTransportError(reason) {
|
|
1180
|
+
return new CapxulError({
|
|
1181
|
+
code: "INTERNAL_ERROR",
|
|
1182
|
+
message: `Internal error: ${reason}`,
|
|
1183
|
+
details: { source: "sdk-transport", reason }
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
async function bootstrapResponseError(response, bootstrapUrl) {
|
|
1187
|
+
const envelope = await readBootstrapErrorEnvelope(response);
|
|
1188
|
+
const wireCode = readNonEmptyString(envelope?.error?.code);
|
|
1189
|
+
const normalized = normalizeBootstrapErrorCode(wireCode);
|
|
1190
|
+
const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
|
|
1191
|
+
const backendDetails = readRecord(envelope?.error?.details);
|
|
1192
|
+
return new CapxulError({
|
|
1193
|
+
code: normalized.code,
|
|
1194
|
+
message,
|
|
1195
|
+
details: {
|
|
1196
|
+
...backendDetails,
|
|
1197
|
+
source: "backend-bootstrap",
|
|
1198
|
+
phase: "publishable-key-bootstrap",
|
|
1199
|
+
httpStatus: response.status,
|
|
1200
|
+
...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
|
|
1201
|
+
},
|
|
1202
|
+
operationId: readNonEmptyString(envelope?.error?.operationId),
|
|
1203
|
+
correlationId: readNonEmptyString(envelope?.error?.correlationId),
|
|
1204
|
+
retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
async function readBootstrapErrorEnvelope(response) {
|
|
1208
|
+
try {
|
|
1209
|
+
const parsed = await response.json();
|
|
1210
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1211
|
+
} catch {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
async function readBootstrapSuccessBody(response) {
|
|
1216
|
+
try {
|
|
1217
|
+
const parsed = await response.json();
|
|
1218
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1219
|
+
} catch {
|
|
1220
|
+
throw bootstrapContractError(
|
|
1221
|
+
"body",
|
|
1222
|
+
"/v1/client/bootstrap returned invalid JSON."
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function normalizeBootstrapThrownError(error) {
|
|
1227
|
+
if (error instanceof CapxulError) return error;
|
|
1228
|
+
return new CapxulError({
|
|
1229
|
+
code: "NETWORK_ERROR",
|
|
1230
|
+
message: "Publishable-key bootstrap network failure.",
|
|
1231
|
+
cause: error,
|
|
1232
|
+
details: {
|
|
1233
|
+
source: "bootstrap-network",
|
|
1234
|
+
phase: "publishable-key-bootstrap"
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
function normalizeBootstrapErrorCode(wireCode) {
|
|
1239
|
+
if (wireCode === "INTERNAL_SERVER_ERROR") {
|
|
1240
|
+
return { code: "INTERNAL_ERROR", wireCode };
|
|
1241
|
+
}
|
|
1242
|
+
if (wireCode && isCapxulErrorCode(wireCode)) {
|
|
1243
|
+
return { code: wireCode };
|
|
1244
|
+
}
|
|
1245
|
+
return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
|
|
1246
|
+
}
|
|
1247
|
+
var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1248
|
+
"NOT_AUTHENTICATED",
|
|
1249
|
+
"EMAIL_DELIVERY_FAILED",
|
|
1250
|
+
"PROFILE_NOT_FOUND",
|
|
1251
|
+
"SMART_ACCOUNT_MISSING",
|
|
1252
|
+
"PLAYER_NOT_FOUND",
|
|
1253
|
+
"ACCOUNT_NOT_FOUND",
|
|
1254
|
+
"PROVIDER_ERROR",
|
|
1255
|
+
"INVALID_INPUT",
|
|
1256
|
+
"ENV_MISSING",
|
|
1257
|
+
"NOT_IMPLEMENTED",
|
|
1258
|
+
"VERIFICATION_REQUIRED",
|
|
1259
|
+
"INSUFFICIENT_BALANCE",
|
|
1260
|
+
"INVALID_RECIPIENT",
|
|
1261
|
+
"TRANSACTION_FAILED",
|
|
1262
|
+
"RATE_LIMITED",
|
|
1263
|
+
"NETWORK_ERROR",
|
|
1264
|
+
"UNKNOWN",
|
|
1265
|
+
"PERMISSION_DENIED",
|
|
1266
|
+
"API_KEY_INVALID",
|
|
1267
|
+
"API_KEY_EXPIRED",
|
|
1268
|
+
"IDEMPOTENCY_CONFLICT",
|
|
1269
|
+
"NOT_FOUND",
|
|
1270
|
+
"OPERATION_CANCELED",
|
|
1271
|
+
"OPERATION_TIMEOUT",
|
|
1272
|
+
"ACTION_REQUIRED",
|
|
1273
|
+
"KYC_REQUIRED",
|
|
1274
|
+
"POLICY_DENIED",
|
|
1275
|
+
"SAFE_NOT_READY",
|
|
1276
|
+
"PROVIDER_UNAVAILABLE",
|
|
1277
|
+
"PROVIDER_REJECTED",
|
|
1278
|
+
"RECONCILIATION_FAILED",
|
|
1279
|
+
"INTERNAL_ERROR",
|
|
1280
|
+
"QUOTE_EXPIRED",
|
|
1281
|
+
"QUOTE_NOT_FOUND"
|
|
1282
|
+
]);
|
|
1283
|
+
function isCapxulErrorCode(value) {
|
|
1284
|
+
return CAPXUL_ERROR_CODES.has(value);
|
|
1285
|
+
}
|
|
1286
|
+
function readRecord(value) {
|
|
1287
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
1288
|
+
}
|
|
1289
|
+
function readNonEmptyString(value) {
|
|
1290
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1291
|
+
}
|
|
459
1292
|
|
|
460
1293
|
// src/core/auth.ts
|
|
461
1294
|
function createAuthClient(config = {}) {
|
|
@@ -510,13 +1343,16 @@ function createAuthClient(config = {}) {
|
|
|
510
1343
|
email: signIn.user.email,
|
|
511
1344
|
token: signIn.token,
|
|
512
1345
|
convexJwt,
|
|
513
|
-
expiresAt: new Date(
|
|
1346
|
+
expiresAt: new Date(
|
|
1347
|
+
Date.now() + 30 * 24 * 60 * 60 * 1e3
|
|
1348
|
+
).toISOString()
|
|
514
1349
|
};
|
|
515
1350
|
sessionStore.set(session);
|
|
516
1351
|
if (config.auth?.createDataClient) {
|
|
517
1352
|
try {
|
|
518
1353
|
dataClient = await config.auth.createDataClient(session);
|
|
519
1354
|
mutableConfig(config).data = dataClient;
|
|
1355
|
+
transport.markAuthenticated({ dataClient });
|
|
520
1356
|
} catch (cause) {
|
|
521
1357
|
return [
|
|
522
1358
|
new CapxulError({
|
|
@@ -528,13 +1364,76 @@ function createAuthClient(config = {}) {
|
|
|
528
1364
|
];
|
|
529
1365
|
}
|
|
530
1366
|
}
|
|
531
|
-
|
|
1367
|
+
if (!dataClient) {
|
|
1368
|
+
return [
|
|
1369
|
+
new CapxulError({
|
|
1370
|
+
code: "NOT_AUTHENTICATED",
|
|
1371
|
+
message: "Auth bootstrap requires an authenticated Convex data client."
|
|
1372
|
+
}),
|
|
1373
|
+
null
|
|
1374
|
+
];
|
|
1375
|
+
}
|
|
1376
|
+
try {
|
|
1377
|
+
const resolution = await dataClient.mutation(
|
|
1378
|
+
api.authBootstrap.resolveAfterOtp,
|
|
1379
|
+
{
|
|
1380
|
+
email: session.email,
|
|
1381
|
+
sessionToken: session.token
|
|
1382
|
+
}
|
|
1383
|
+
);
|
|
1384
|
+
if (resolution.kind === "existing_member") {
|
|
1385
|
+
return [null, { ...resolution, session }];
|
|
1386
|
+
}
|
|
1387
|
+
return [null, { ...resolution, session }];
|
|
1388
|
+
} catch (cause) {
|
|
1389
|
+
return [fromConvexError(cause), null];
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
completeBootstrap: async (input) => {
|
|
1393
|
+
const session = sessionStore.get();
|
|
1394
|
+
const data = dataClient ?? config.data;
|
|
1395
|
+
if (!session || !data) {
|
|
1396
|
+
return [
|
|
1397
|
+
new CapxulError({
|
|
1398
|
+
code: "INVALID_INPUT",
|
|
1399
|
+
message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
|
|
1400
|
+
}),
|
|
1401
|
+
null
|
|
1402
|
+
];
|
|
1403
|
+
}
|
|
1404
|
+
if (input.signerProvider.kind !== "local-private-key") {
|
|
1405
|
+
return [
|
|
1406
|
+
new CapxulError({
|
|
1407
|
+
code: "INVALID_INPUT",
|
|
1408
|
+
message: "completeBootstrap currently supports local-private-key signer providers only."
|
|
1409
|
+
}),
|
|
1410
|
+
null
|
|
1411
|
+
];
|
|
1412
|
+
}
|
|
1413
|
+
try {
|
|
1414
|
+
const result = await data.mutation(api.authBootstrap.completeBootstrap, {
|
|
1415
|
+
bootstrapToken: input.bootstrapToken,
|
|
1416
|
+
sessionToken: session.token,
|
|
1417
|
+
username: input.username,
|
|
1418
|
+
displayName: input.displayName,
|
|
1419
|
+
countryCode: input.countryCode,
|
|
1420
|
+
signerProvider: input.signerProvider
|
|
1421
|
+
});
|
|
1422
|
+
return [null, { kind: "authenticated", session, ...result }];
|
|
1423
|
+
} catch (cause) {
|
|
1424
|
+
return [
|
|
1425
|
+
fromConvexError(cause),
|
|
1426
|
+
null
|
|
1427
|
+
];
|
|
1428
|
+
}
|
|
532
1429
|
},
|
|
533
1430
|
getSession: async () => [null, sessionStore.get()],
|
|
534
1431
|
signOut: async () => {
|
|
535
1432
|
sessionStore.clear();
|
|
536
1433
|
dataClient = null;
|
|
537
1434
|
mutableConfig(config).data = void 0;
|
|
1435
|
+
const transport = getTransport();
|
|
1436
|
+
transport?.clearAuth();
|
|
538
1437
|
return [null, void 0];
|
|
539
1438
|
},
|
|
540
1439
|
serviceTokenMint: async () => stub("auth.serviceTokenMint"),
|
|
@@ -588,16 +1487,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
588
1487
|
body: JSON.stringify(body),
|
|
589
1488
|
signal
|
|
590
1489
|
});
|
|
1490
|
+
const text = await response.text();
|
|
591
1491
|
if (!response.ok) {
|
|
1492
|
+
const parsedError = parseBetterAuthError(text);
|
|
592
1493
|
return [
|
|
593
1494
|
new CapxulError({
|
|
594
|
-
code,
|
|
595
|
-
message: `BetterAuth ${path} failed with HTTP ${response.status}
|
|
1495
|
+
code: parsedError.code ?? code,
|
|
1496
|
+
message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
|
|
1497
|
+
details: parsedError.details,
|
|
1498
|
+
retryable: parsedError.retryable
|
|
596
1499
|
}),
|
|
597
1500
|
null
|
|
598
1501
|
];
|
|
599
1502
|
}
|
|
600
|
-
const text = await response.text();
|
|
601
1503
|
return [null, text ? JSON.parse(text) : void 0];
|
|
602
1504
|
} catch (cause) {
|
|
603
1505
|
return [
|
|
@@ -610,6 +1512,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
610
1512
|
];
|
|
611
1513
|
}
|
|
612
1514
|
}
|
|
1515
|
+
function parseBetterAuthError(text) {
|
|
1516
|
+
if (!text.trim()) {
|
|
1517
|
+
return {};
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
const body = JSON.parse(text);
|
|
1521
|
+
if (!body || typeof body !== "object") {
|
|
1522
|
+
return {};
|
|
1523
|
+
}
|
|
1524
|
+
const record = body;
|
|
1525
|
+
const nested = record.error && typeof record.error === "object" ? record.error : record;
|
|
1526
|
+
const code = typeof nested.code === "string" ? nested.code : void 0;
|
|
1527
|
+
const message = typeof nested.message === "string" ? nested.message : void 0;
|
|
1528
|
+
const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
|
|
1529
|
+
const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
|
|
1530
|
+
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
1531
|
+
return {
|
|
1532
|
+
code: isCapxulErrorCode2(code) ? code : void 0,
|
|
1533
|
+
message,
|
|
1534
|
+
details,
|
|
1535
|
+
correlationId,
|
|
1536
|
+
retryable
|
|
1537
|
+
};
|
|
1538
|
+
} catch {
|
|
1539
|
+
return {};
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function isCapxulErrorCode2(code) {
|
|
1543
|
+
return code === "NOT_AUTHENTICATED" || code === "EMAIL_DELIVERY_FAILED" || code === "INVALID_INPUT" || code === "RATE_LIMITED" || code === "NETWORK_ERROR" || code === "API_KEY_INVALID" || code === "API_KEY_EXPIRED" || code === "INTERNAL_ERROR" || code === "ENV_MISSING" || code === "UNKNOWN" || code === "PROVIDER_ERROR";
|
|
1544
|
+
}
|
|
613
1545
|
async function exchangeConvexToken(transport, config, token, signal) {
|
|
614
1546
|
const path = config.auth?.convexTokenUrl ?? "/convex/token";
|
|
615
1547
|
try {
|
|
@@ -670,22 +1602,42 @@ function createOrgDocumentsClient() {
|
|
|
670
1602
|
};
|
|
671
1603
|
}
|
|
672
1604
|
|
|
673
|
-
// src/core/external-accounts.ts
|
|
674
|
-
function createExternalAccountsClient() {
|
|
675
|
-
return {
|
|
676
|
-
retrieve: async () => stub("externalAccounts.retrieve"),
|
|
677
|
-
remove: async () => stub("externalAccounts.remove")
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
1605
|
// src/core/me.ts
|
|
682
1606
|
function createMeClient(config = {}) {
|
|
683
1607
|
return {
|
|
684
1608
|
get: async () => {
|
|
685
1609
|
if (!config.data) {
|
|
686
|
-
return stub("me.get");
|
|
1610
|
+
return stub("me.get");
|
|
1611
|
+
}
|
|
1612
|
+
try {
|
|
1613
|
+
const account = await config.data.query(
|
|
1614
|
+
api.openfort.queries.getMyAccount,
|
|
1615
|
+
{}
|
|
1616
|
+
);
|
|
1617
|
+
return [null, account];
|
|
1618
|
+
} catch (cause) {
|
|
1619
|
+
return [fromConvexError(cause), null];
|
|
1620
|
+
}
|
|
1621
|
+
},
|
|
1622
|
+
update: async (input) => {
|
|
1623
|
+
if (!config.data) {
|
|
1624
|
+
return stub("me.update");
|
|
1625
|
+
}
|
|
1626
|
+
if (input.countryCode !== void 0) {
|
|
1627
|
+
return [
|
|
1628
|
+
new CapxulError({
|
|
1629
|
+
code: "INVALID_INPUT",
|
|
1630
|
+
message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
1631
|
+
details: { field: "countryCode" }
|
|
1632
|
+
}),
|
|
1633
|
+
null
|
|
1634
|
+
];
|
|
687
1635
|
}
|
|
688
1636
|
try {
|
|
1637
|
+
await config.data.mutation(api.openfort.mutations.updateProfile, {
|
|
1638
|
+
displayName: input.name,
|
|
1639
|
+
username: input.username
|
|
1640
|
+
});
|
|
689
1641
|
const account = await config.data.query(
|
|
690
1642
|
api.openfort.queries.getMyAccount,
|
|
691
1643
|
{}
|
|
@@ -694,8 +1646,7 @@ function createMeClient(config = {}) {
|
|
|
694
1646
|
} catch (cause) {
|
|
695
1647
|
return [fromConvexError(cause), null];
|
|
696
1648
|
}
|
|
697
|
-
}
|
|
698
|
-
update: async () => stub("me.update")
|
|
1649
|
+
}
|
|
699
1650
|
};
|
|
700
1651
|
}
|
|
701
1652
|
|
|
@@ -1033,36 +1984,430 @@ function createPaymentsClient(config = {}) {
|
|
|
1033
1984
|
list: async () => stub("payments.list")
|
|
1034
1985
|
};
|
|
1035
1986
|
}
|
|
1036
|
-
function createOrgPaymentsClient() {
|
|
1037
|
-
return {
|
|
1038
|
-
create: async () => stub(
|
|
1039
|
-
"organizations.payments.create"
|
|
1040
|
-
),
|
|
1041
|
-
retrieve: async () => stub("organizations.payments.retrieve"),
|
|
1042
|
-
list: async () => stub("organizations.payments.list")
|
|
1043
|
-
};
|
|
1987
|
+
function createOrgPaymentsClient() {
|
|
1988
|
+
return {
|
|
1989
|
+
create: async () => stub(
|
|
1990
|
+
"organizations.payments.create"
|
|
1991
|
+
),
|
|
1992
|
+
retrieve: async () => stub("organizations.payments.retrieve"),
|
|
1993
|
+
list: async () => stub("organizations.payments.list")
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
async function bestEffortMarkFailed(config, paymentId, error) {
|
|
1997
|
+
try {
|
|
1998
|
+
await config.data.mutation(api.payments.mutations.markFailed, {
|
|
1999
|
+
paymentId,
|
|
2000
|
+
errorCode: error.code,
|
|
2001
|
+
errorMessage: error.message,
|
|
2002
|
+
source: "sdk"
|
|
2003
|
+
});
|
|
2004
|
+
} catch {
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function mapCreateError(error) {
|
|
2008
|
+
switch (error.code) {
|
|
2009
|
+
case "NOT_AUTHENTICATED":
|
|
2010
|
+
case "PERMISSION_DENIED":
|
|
2011
|
+
case "INVALID_INPUT":
|
|
2012
|
+
case "INVALID_RECIPIENT":
|
|
2013
|
+
case "INSUFFICIENT_BALANCE":
|
|
2014
|
+
case "IDEMPOTENCY_CONFLICT":
|
|
2015
|
+
case "RATE_LIMITED":
|
|
2016
|
+
case "NETWORK_ERROR":
|
|
2017
|
+
return error;
|
|
2018
|
+
default:
|
|
2019
|
+
return new CapxulError({
|
|
2020
|
+
code: "NETWORK_ERROR",
|
|
2021
|
+
message: error.message,
|
|
2022
|
+
cause: error,
|
|
2023
|
+
details: error.details,
|
|
2024
|
+
operationId: error.operationId,
|
|
2025
|
+
correlationId: error.correlationId,
|
|
2026
|
+
retryable: error.retryable
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// src/core/transfers.ts
|
|
2032
|
+
function createTransfersClient() {
|
|
2033
|
+
return {
|
|
2034
|
+
create: async () => stub("transfers.create"),
|
|
2035
|
+
retrieve: async () => stub("transfers.retrieve"),
|
|
2036
|
+
list: async () => stub("transfers.list"),
|
|
2037
|
+
confirm: async () => stub("transfers.confirm"),
|
|
2038
|
+
cancel: async () => stub("transfers.cancel")
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
function createOrgTransfersClient() {
|
|
2042
|
+
return {
|
|
2043
|
+
create: async () => stub(
|
|
2044
|
+
"organizations.transfers.create"
|
|
2045
|
+
),
|
|
2046
|
+
retrieve: async () => stub("organizations.transfers.retrieve"),
|
|
2047
|
+
list: async () => stub("organizations.transfers.list"),
|
|
2048
|
+
confirm: async () => stub("organizations.transfers.confirm"),
|
|
2049
|
+
cancel: async () => stub("organizations.transfers.cancel")
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
function createWithdrawalsClient(config = {}) {
|
|
2053
|
+
return {
|
|
2054
|
+
create: async (input) => {
|
|
2055
|
+
if (!config.data) {
|
|
2056
|
+
return stub("withdrawals.create");
|
|
2057
|
+
}
|
|
2058
|
+
const [createErr, createdRaw] = await tryCatch(
|
|
2059
|
+
config.data.mutation(api.withdrawals.mutations.create, {
|
|
2060
|
+
amount: input.amount,
|
|
2061
|
+
destination: {
|
|
2062
|
+
externalAccountId: input.destination.externalAccountId
|
|
2063
|
+
},
|
|
2064
|
+
source: input.source,
|
|
2065
|
+
reference: input.reference,
|
|
2066
|
+
idempotencyKey: input.idempotencyKey
|
|
2067
|
+
})
|
|
2068
|
+
);
|
|
2069
|
+
if (createErr) {
|
|
2070
|
+
return [mapCreateError2(fromConvexError(createErr)), null];
|
|
2071
|
+
}
|
|
2072
|
+
const created = createdRaw;
|
|
2073
|
+
if (!created) {
|
|
2074
|
+
return [
|
|
2075
|
+
new CapxulError({
|
|
2076
|
+
code: "NETWORK_ERROR",
|
|
2077
|
+
message: "withdrawals.create returned no withdrawal resource"
|
|
2078
|
+
}),
|
|
2079
|
+
null
|
|
2080
|
+
];
|
|
2081
|
+
}
|
|
2082
|
+
if (created.status !== "processing" || created.operation.status !== "processing") {
|
|
2083
|
+
return [null, created];
|
|
2084
|
+
}
|
|
2085
|
+
if (!config.signer || !config.signing) {
|
|
2086
|
+
return [null, created];
|
|
2087
|
+
}
|
|
2088
|
+
const [signerErr, currentSigner] = await tryCatch(
|
|
2089
|
+
config.data.query(api.safe.queries.getMySignerAddress, {})
|
|
2090
|
+
);
|
|
2091
|
+
if (signerErr) {
|
|
2092
|
+
return await handleSubmissionFailure(
|
|
2093
|
+
{ data: config.data },
|
|
2094
|
+
created.id,
|
|
2095
|
+
mapCreateError2(fromConvexError(signerErr))
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
if (!currentSigner?.address) {
|
|
2099
|
+
return await handleSubmissionFailure(
|
|
2100
|
+
{ data: config.data },
|
|
2101
|
+
created.id,
|
|
2102
|
+
new CapxulError({
|
|
2103
|
+
code: "PERMISSION_DENIED",
|
|
2104
|
+
message: "No signer is registered for the authenticated account.",
|
|
2105
|
+
details: { withdrawalId: created.id }
|
|
2106
|
+
})
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
|
|
2110
|
+
return await handleSubmissionFailure(
|
|
2111
|
+
{ data: config.data },
|
|
2112
|
+
created.id,
|
|
2113
|
+
new CapxulError({
|
|
2114
|
+
code: "PERMISSION_DENIED",
|
|
2115
|
+
message: "Configured signer does not match the authenticated account signer.",
|
|
2116
|
+
details: {
|
|
2117
|
+
withdrawalId: created.id,
|
|
2118
|
+
expectedSignerAddress: currentSigner.address,
|
|
2119
|
+
actualSignerAddress: config.signer.address
|
|
2120
|
+
}
|
|
2121
|
+
})
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
const [prepErr, submission] = await tryCatch(
|
|
2125
|
+
config.data.query(api.withdrawals.queries.prepareSubmission, {
|
|
2126
|
+
withdrawalId: created.id
|
|
2127
|
+
})
|
|
2128
|
+
);
|
|
2129
|
+
if (prepErr) {
|
|
2130
|
+
return await handleSubmissionFailure(
|
|
2131
|
+
{ data: config.data },
|
|
2132
|
+
created.id,
|
|
2133
|
+
mapCreateError2(fromConvexError(prepErr))
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
const destinationAddress = submission?.destinationAddress;
|
|
2137
|
+
if (!submission || !destinationAddress) {
|
|
2138
|
+
return await handleSubmissionFailure(
|
|
2139
|
+
{ data: config.data },
|
|
2140
|
+
created.id,
|
|
2141
|
+
new CapxulError({
|
|
2142
|
+
code: "NETWORK_ERROR",
|
|
2143
|
+
message: "withdrawals.prepareSubmission returned no destination.",
|
|
2144
|
+
details: { withdrawalId: created.id }
|
|
2145
|
+
})
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
const [transferErr, transferOk] = await tryCatch(
|
|
2149
|
+
transferAsOwner(
|
|
2150
|
+
{
|
|
2151
|
+
signer: config.signer,
|
|
2152
|
+
signing: config.signing
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
|
|
2156
|
+
recipientAddress: destinationAddress,
|
|
2157
|
+
amount: toTokenUnits(submission.amount.value, 6)
|
|
2158
|
+
}
|
|
2159
|
+
)
|
|
2160
|
+
);
|
|
2161
|
+
if (transferErr) {
|
|
2162
|
+
return await handleSubmissionFailure(
|
|
2163
|
+
{ data: config.data },
|
|
2164
|
+
created.id,
|
|
2165
|
+
mapCreateError2(fromConvexError(transferErr))
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
if (!transferOk.success) {
|
|
2169
|
+
return await handleSubmissionFailure(
|
|
2170
|
+
{ data: config.data },
|
|
2171
|
+
created.id,
|
|
2172
|
+
new CapxulError({
|
|
2173
|
+
code: "NETWORK_ERROR",
|
|
2174
|
+
message: "Bundler submission did not succeed.",
|
|
2175
|
+
details: {
|
|
2176
|
+
withdrawalId: created.id,
|
|
2177
|
+
txHash: transferOk.txHash,
|
|
2178
|
+
userOpHash: transferOk.userOpHash
|
|
2179
|
+
}
|
|
2180
|
+
})
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
const [recordErr] = await tryCatch(
|
|
2184
|
+
config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
|
|
2185
|
+
withdrawalId: created.id,
|
|
2186
|
+
txHash: transferOk.txHash,
|
|
2187
|
+
userOpHash: transferOk.userOpHash
|
|
2188
|
+
})
|
|
2189
|
+
);
|
|
2190
|
+
if (recordErr) {
|
|
2191
|
+
return [
|
|
2192
|
+
new CapxulError({
|
|
2193
|
+
code: "NETWORK_ERROR",
|
|
2194
|
+
message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
|
|
2195
|
+
cause: recordErr,
|
|
2196
|
+
details: {
|
|
2197
|
+
withdrawalId: created.id,
|
|
2198
|
+
txHash: transferOk.txHash,
|
|
2199
|
+
userOpHash: transferOk.userOpHash
|
|
2200
|
+
}
|
|
2201
|
+
}),
|
|
2202
|
+
null
|
|
2203
|
+
];
|
|
2204
|
+
}
|
|
2205
|
+
return [null, created];
|
|
2206
|
+
},
|
|
2207
|
+
retrieve: async (withdrawalId) => {
|
|
2208
|
+
if (!config.data) {
|
|
2209
|
+
return stub("withdrawals.retrieve");
|
|
2210
|
+
}
|
|
2211
|
+
const [err, raw] = await tryCatch(
|
|
2212
|
+
config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
|
|
2213
|
+
);
|
|
2214
|
+
if (err) {
|
|
2215
|
+
return [fromConvexError(err), null];
|
|
2216
|
+
}
|
|
2217
|
+
const withdrawal = raw;
|
|
2218
|
+
if (!withdrawal) {
|
|
2219
|
+
return [
|
|
2220
|
+
new CapxulError({
|
|
2221
|
+
code: "NOT_FOUND",
|
|
2222
|
+
message: `withdrawal ${withdrawalId} not found`
|
|
2223
|
+
}),
|
|
2224
|
+
null
|
|
2225
|
+
];
|
|
2226
|
+
}
|
|
2227
|
+
return [null, withdrawal];
|
|
2228
|
+
},
|
|
2229
|
+
list: async (input) => {
|
|
2230
|
+
if (!config.data) {
|
|
2231
|
+
return stub("withdrawals.list");
|
|
2232
|
+
}
|
|
2233
|
+
const [err, raw] = await tryCatch(
|
|
2234
|
+
config.data.query(api.withdrawals.queries.list, {
|
|
2235
|
+
limit: input?.limit,
|
|
2236
|
+
cursor: input?.cursor
|
|
2237
|
+
})
|
|
2238
|
+
);
|
|
2239
|
+
if (err) {
|
|
2240
|
+
return [fromConvexError(err), null];
|
|
2241
|
+
}
|
|
2242
|
+
return [null, raw];
|
|
2243
|
+
},
|
|
2244
|
+
recordCompleted: async (input) => {
|
|
2245
|
+
if (!config.data) {
|
|
2246
|
+
return stub(
|
|
2247
|
+
"withdrawals.recordCompleted"
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
const [err] = await tryCatch(
|
|
2251
|
+
config.data.mutation(api.withdrawals.mutations.recordCompleted, {
|
|
2252
|
+
withdrawalId: input.withdrawalId,
|
|
2253
|
+
txHash: input.txHash
|
|
2254
|
+
})
|
|
2255
|
+
);
|
|
2256
|
+
if (err) {
|
|
2257
|
+
return [
|
|
2258
|
+
mapRecordCompletedError(fromConvexError(err)),
|
|
2259
|
+
null
|
|
2260
|
+
];
|
|
2261
|
+
}
|
|
2262
|
+
return [null, null];
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function createOrgWithdrawalsClient(config = {}) {
|
|
2267
|
+
return {
|
|
2268
|
+
/**
|
|
2269
|
+
* Org-scope create (Withdrawals v1 W2, #465).
|
|
2270
|
+
*
|
|
2271
|
+
* D6 — returns the `processing` row only. No `transferAsOwner`
|
|
2272
|
+
* tail, no `recordSubmitted` call. Org Safe + Zodiac submission
|
|
2273
|
+
* orchestration ships in W3+.
|
|
2274
|
+
*/
|
|
2275
|
+
create: async (input) => {
|
|
2276
|
+
if (!config.data) {
|
|
2277
|
+
return stub(
|
|
2278
|
+
"organizations.withdrawals.create"
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
const [err, raw] = await tryCatch(
|
|
2282
|
+
config.data.mutation(api.withdrawals.mutations.createOrg, {
|
|
2283
|
+
organizationId: input.organizationId,
|
|
2284
|
+
amount: input.amount,
|
|
2285
|
+
destination: {
|
|
2286
|
+
externalAccountId: input.destination.externalAccountId
|
|
2287
|
+
},
|
|
2288
|
+
source: input.source,
|
|
2289
|
+
reference: input.reference,
|
|
2290
|
+
idempotencyKey: input.idempotencyKey
|
|
2291
|
+
})
|
|
2292
|
+
);
|
|
2293
|
+
if (err) {
|
|
2294
|
+
return [mapCreateError2(fromConvexError(err)), null];
|
|
2295
|
+
}
|
|
2296
|
+
const created = raw;
|
|
2297
|
+
if (!created) {
|
|
2298
|
+
return [
|
|
2299
|
+
new CapxulError({
|
|
2300
|
+
code: "NETWORK_ERROR",
|
|
2301
|
+
message: "organizations.withdrawals.create returned no withdrawal resource"
|
|
2302
|
+
}),
|
|
2303
|
+
null
|
|
2304
|
+
];
|
|
2305
|
+
}
|
|
2306
|
+
return [null, created];
|
|
2307
|
+
},
|
|
2308
|
+
retrieve: async (input) => {
|
|
2309
|
+
if (!config.data) {
|
|
2310
|
+
return stub(
|
|
2311
|
+
"organizations.withdrawals.retrieve"
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
const [err, raw] = await tryCatch(
|
|
2315
|
+
config.data.query(api.withdrawals.queries.retrieve, {
|
|
2316
|
+
withdrawalId: input.withdrawalId
|
|
2317
|
+
})
|
|
2318
|
+
);
|
|
2319
|
+
if (err) {
|
|
2320
|
+
return [fromConvexError(err), null];
|
|
2321
|
+
}
|
|
2322
|
+
const withdrawal = raw;
|
|
2323
|
+
if (!withdrawal) {
|
|
2324
|
+
return [
|
|
2325
|
+
new CapxulError({
|
|
2326
|
+
code: "NOT_FOUND",
|
|
2327
|
+
message: `withdrawal ${input.withdrawalId} not found`
|
|
2328
|
+
}),
|
|
2329
|
+
null
|
|
2330
|
+
];
|
|
2331
|
+
}
|
|
2332
|
+
const ownerCheck = withdrawal.owner;
|
|
2333
|
+
if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
|
|
2334
|
+
return [
|
|
2335
|
+
new CapxulError({
|
|
2336
|
+
code: "NOT_FOUND",
|
|
2337
|
+
message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
|
|
2338
|
+
}),
|
|
2339
|
+
null
|
|
2340
|
+
];
|
|
2341
|
+
}
|
|
2342
|
+
return [null, withdrawal];
|
|
2343
|
+
},
|
|
2344
|
+
list: async (input) => {
|
|
2345
|
+
if (!config.data) {
|
|
2346
|
+
return stub(
|
|
2347
|
+
"organizations.withdrawals.list"
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
const [err, raw] = await tryCatch(
|
|
2351
|
+
config.data.query(api.withdrawals.queries.listOrg, {
|
|
2352
|
+
organizationId: input.organizationId,
|
|
2353
|
+
limit: input.limit,
|
|
2354
|
+
cursor: input.cursor
|
|
2355
|
+
})
|
|
2356
|
+
);
|
|
2357
|
+
if (err) {
|
|
2358
|
+
return [fromConvexError(err), null];
|
|
2359
|
+
}
|
|
2360
|
+
return [null, raw];
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
async function handleSubmissionFailure(config, withdrawalId, error) {
|
|
2365
|
+
await bestEffortMarkFailed2(config, withdrawalId, error);
|
|
2366
|
+
return [error, null];
|
|
1044
2367
|
}
|
|
1045
|
-
async function
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
2368
|
+
async function bestEffortMarkFailed2(config, withdrawalId, error) {
|
|
2369
|
+
await tryCatch(
|
|
2370
|
+
config.data.mutation(api.withdrawals.mutations.markFailed, {
|
|
2371
|
+
withdrawalId,
|
|
1049
2372
|
errorCode: error.code,
|
|
1050
|
-
errorMessage: error.message
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
} catch {
|
|
1054
|
-
}
|
|
2373
|
+
errorMessage: error.message
|
|
2374
|
+
})
|
|
2375
|
+
);
|
|
1055
2376
|
}
|
|
1056
|
-
function
|
|
2377
|
+
function mapCreateError2(error) {
|
|
1057
2378
|
switch (error.code) {
|
|
1058
2379
|
case "NOT_AUTHENTICATED":
|
|
1059
2380
|
case "PERMISSION_DENIED":
|
|
1060
2381
|
case "INVALID_INPUT":
|
|
1061
|
-
case "INVALID_RECIPIENT":
|
|
1062
2382
|
case "INSUFFICIENT_BALANCE":
|
|
1063
2383
|
case "IDEMPOTENCY_CONFLICT":
|
|
2384
|
+
case "KYC_REQUIRED":
|
|
2385
|
+
case "POLICY_DENIED":
|
|
1064
2386
|
case "RATE_LIMITED":
|
|
1065
2387
|
case "NETWORK_ERROR":
|
|
2388
|
+
case "NOT_FOUND":
|
|
2389
|
+
case "VERIFICATION_REQUIRED":
|
|
2390
|
+
return error;
|
|
2391
|
+
default:
|
|
2392
|
+
return new CapxulError({
|
|
2393
|
+
code: "NETWORK_ERROR",
|
|
2394
|
+
message: error.message,
|
|
2395
|
+
cause: error,
|
|
2396
|
+
details: error.details,
|
|
2397
|
+
operationId: error.operationId,
|
|
2398
|
+
correlationId: error.correlationId,
|
|
2399
|
+
retryable: error.retryable
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
function mapRecordCompletedError(error) {
|
|
2404
|
+
switch (error.code) {
|
|
2405
|
+
case "NOT_AUTHENTICATED":
|
|
2406
|
+
case "PERMISSION_DENIED":
|
|
2407
|
+
case "INVALID_INPUT":
|
|
2408
|
+
case "NOT_FOUND":
|
|
2409
|
+
case "NETWORK_ERROR":
|
|
2410
|
+
case "INTERNAL_ERROR":
|
|
1066
2411
|
return error;
|
|
1067
2412
|
default:
|
|
1068
2413
|
return new CapxulError({
|
|
@@ -1077,341 +2422,308 @@ function mapCreateError(error) {
|
|
|
1077
2422
|
}
|
|
1078
2423
|
}
|
|
1079
2424
|
|
|
1080
|
-
// src/core/
|
|
1081
|
-
function
|
|
2425
|
+
// src/core/webhook-endpoints.ts
|
|
2426
|
+
function createWebhookEndpointsClient() {
|
|
1082
2427
|
return {
|
|
1083
|
-
create: async () => stub(
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
2428
|
+
create: async () => stub(
|
|
2429
|
+
"webhookEndpoints.create"
|
|
2430
|
+
),
|
|
2431
|
+
retrieve: async () => stub("webhookEndpoints.retrieve"),
|
|
2432
|
+
list: async () => stub("webhookEndpoints.list"),
|
|
2433
|
+
remove: async () => stub("webhookEndpoints.remove")
|
|
1088
2434
|
};
|
|
1089
2435
|
}
|
|
1090
|
-
|
|
2436
|
+
|
|
2437
|
+
// src/core/webhook-events.ts
|
|
2438
|
+
function createWebhookEventsClient() {
|
|
1091
2439
|
return {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
),
|
|
1095
|
-
retrieve: async () => stub("organizations.transfers.retrieve"),
|
|
1096
|
-
list: async () => stub("organizations.transfers.list"),
|
|
1097
|
-
confirm: async () => stub("organizations.transfers.confirm"),
|
|
1098
|
-
cancel: async () => stub("organizations.transfers.cancel")
|
|
2440
|
+
retrieve: async () => stub("webhookEvents.retrieve"),
|
|
2441
|
+
list: async () => stub("webhookEvents.list")
|
|
1099
2442
|
};
|
|
1100
2443
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
2444
|
+
|
|
2445
|
+
// src/core/organizations.ts
|
|
2446
|
+
function createOrgExternalAccountsClient(config) {
|
|
1103
2447
|
return {
|
|
1104
2448
|
create: async (input) => {
|
|
1105
2449
|
if (!config.data) {
|
|
1106
|
-
return stub(
|
|
1107
|
-
|
|
1108
|
-
let created = null;
|
|
1109
|
-
let submitted = null;
|
|
1110
|
-
try {
|
|
1111
|
-
created = await config.data.mutation(
|
|
1112
|
-
api.withdrawals.mutations.create,
|
|
1113
|
-
{
|
|
1114
|
-
amount: input.amount,
|
|
1115
|
-
destination: {
|
|
1116
|
-
externalAccountId: input.destination.externalAccountId,
|
|
1117
|
-
kind: input.destination.kind
|
|
1118
|
-
},
|
|
1119
|
-
source: input.source,
|
|
1120
|
-
reference: input.reference,
|
|
1121
|
-
idempotencyKey: input.idempotencyKey
|
|
1122
|
-
}
|
|
1123
|
-
);
|
|
1124
|
-
if (!created) {
|
|
1125
|
-
return [
|
|
1126
|
-
new CapxulError({
|
|
1127
|
-
code: "NETWORK_ERROR",
|
|
1128
|
-
message: "withdrawals.create returned no withdrawal resource"
|
|
1129
|
-
}),
|
|
1130
|
-
null
|
|
1131
|
-
];
|
|
1132
|
-
}
|
|
1133
|
-
if (created.status !== "processing" || created.operation.status !== "processing") {
|
|
1134
|
-
return [null, created];
|
|
1135
|
-
}
|
|
1136
|
-
if (input.destination.kind !== "evm") {
|
|
1137
|
-
return [null, created];
|
|
1138
|
-
}
|
|
1139
|
-
if (!config.signer || !config.signing) {
|
|
1140
|
-
return [null, created];
|
|
1141
|
-
}
|
|
1142
|
-
if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
|
|
1143
|
-
throw new CapxulError({
|
|
1144
|
-
code: "INVALID_INPUT",
|
|
1145
|
-
message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
|
|
1146
|
-
details: { field: "destination.externalAccountId" }
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
const currentSigner = await config.data.query(
|
|
1150
|
-
api.safe.queries.getMySignerAddress,
|
|
1151
|
-
{}
|
|
1152
|
-
);
|
|
1153
|
-
if (!currentSigner?.address) {
|
|
1154
|
-
throw new CapxulError({
|
|
1155
|
-
code: "PERMISSION_DENIED",
|
|
1156
|
-
message: "No signer is registered for the authenticated account.",
|
|
1157
|
-
details: { withdrawalId: created.id }
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
|
|
1161
|
-
throw new CapxulError({
|
|
1162
|
-
code: "PERMISSION_DENIED",
|
|
1163
|
-
message: "Configured signer does not match the authenticated account signer.",
|
|
1164
|
-
details: {
|
|
1165
|
-
withdrawalId: created.id,
|
|
1166
|
-
expectedSignerAddress: currentSigner.address,
|
|
1167
|
-
actualSignerAddress: config.signer.address
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
const submission = await config.data.query(
|
|
1172
|
-
api.withdrawals.queries.prepareSubmission,
|
|
1173
|
-
{ withdrawalId: created.id }
|
|
2450
|
+
return stub(
|
|
2451
|
+
"organizations.externalAccounts.create"
|
|
1174
2452
|
);
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
2453
|
+
}
|
|
2454
|
+
const [err, raw] = await tryCatch(
|
|
2455
|
+
config.data.mutation(api.externalAccounts.mutations.createOrg, {
|
|
2456
|
+
organizationId: input.organizationId,
|
|
2457
|
+
kind: input.kind,
|
|
2458
|
+
label: input.label,
|
|
2459
|
+
address: input.address,
|
|
2460
|
+
iban: input.iban,
|
|
2461
|
+
bic: input.bic,
|
|
2462
|
+
accountHolder: input.accountHolder,
|
|
2463
|
+
network: input.network,
|
|
2464
|
+
panToken: input.panToken,
|
|
2465
|
+
last4: input.last4
|
|
2466
|
+
})
|
|
2467
|
+
);
|
|
2468
|
+
if (err) {
|
|
2469
|
+
return [
|
|
2470
|
+
fromConvexError(err),
|
|
2471
|
+
null
|
|
2472
|
+
];
|
|
2473
|
+
}
|
|
2474
|
+
if (!raw) {
|
|
2475
|
+
return [
|
|
2476
|
+
new CapxulError({
|
|
2477
|
+
code: "NOT_FOUND",
|
|
2478
|
+
message: "external_account creation returned no resource"
|
|
2479
|
+
}),
|
|
2480
|
+
null
|
|
2481
|
+
];
|
|
2482
|
+
}
|
|
2483
|
+
return [
|
|
2484
|
+
null,
|
|
2485
|
+
brandExternalAccount(
|
|
2486
|
+
raw
|
|
2487
|
+
)
|
|
2488
|
+
];
|
|
2489
|
+
},
|
|
2490
|
+
list: async (input) => {
|
|
2491
|
+
if (!config.data) {
|
|
2492
|
+
return stub(
|
|
2493
|
+
"organizations.externalAccounts.list"
|
|
1192
2494
|
);
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
2495
|
+
}
|
|
2496
|
+
const [err, result] = await tryCatch(
|
|
2497
|
+
config.data.query(api.externalAccounts.queries.listOrg, {
|
|
2498
|
+
organizationId: input.organizationId,
|
|
2499
|
+
limit: input.limit,
|
|
2500
|
+
cursor: input.cursor
|
|
2501
|
+
})
|
|
2502
|
+
);
|
|
2503
|
+
if (err) {
|
|
2504
|
+
return [fromConvexError(err), null];
|
|
2505
|
+
}
|
|
2506
|
+
const branded = result.data.map(
|
|
2507
|
+
(row) => brandExternalAccount(
|
|
2508
|
+
row
|
|
2509
|
+
)
|
|
2510
|
+
);
|
|
2511
|
+
return [
|
|
2512
|
+
null,
|
|
2513
|
+
{
|
|
2514
|
+
object: "list",
|
|
2515
|
+
data: branded,
|
|
2516
|
+
page: result.page
|
|
1203
2517
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
{
|
|
1211
|
-
withdrawalId: created.id,
|
|
1212
|
-
txHash: transfer.txHash,
|
|
1213
|
-
userOpHash: transfer.userOpHash
|
|
1214
|
-
}
|
|
2518
|
+
];
|
|
2519
|
+
},
|
|
2520
|
+
retrieve: async (input) => {
|
|
2521
|
+
if (!config.data) {
|
|
2522
|
+
return stub(
|
|
2523
|
+
"organizations.externalAccounts.retrieve"
|
|
1215
2524
|
);
|
|
1216
|
-
return [null, created];
|
|
1217
|
-
} catch (cause) {
|
|
1218
|
-
const error = mapCreateError2(fromConvexError(cause));
|
|
1219
|
-
if (created?.id && created.status === "processing" && !submitted) {
|
|
1220
|
-
await bestEffortMarkFailed2({ data: config.data }, created.id, error);
|
|
1221
|
-
}
|
|
1222
|
-
if (submitted && created?.id) {
|
|
1223
|
-
return [
|
|
1224
|
-
new CapxulError({
|
|
1225
|
-
code: "NETWORK_ERROR",
|
|
1226
|
-
message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
|
|
1227
|
-
cause,
|
|
1228
|
-
details: {
|
|
1229
|
-
withdrawalId: created.id,
|
|
1230
|
-
txHash: submitted.txHash,
|
|
1231
|
-
userOpHash: submitted.userOpHash
|
|
1232
|
-
}
|
|
1233
|
-
}),
|
|
1234
|
-
null
|
|
1235
|
-
];
|
|
1236
|
-
}
|
|
1237
|
-
return [error, null];
|
|
1238
2525
|
}
|
|
2526
|
+
const [err, raw] = await tryCatch(
|
|
2527
|
+
config.data.query(api.externalAccounts.queries.retrieveOrg, {
|
|
2528
|
+
organizationId: input.organizationId,
|
|
2529
|
+
externalAccountId: input.externalAccountId
|
|
2530
|
+
})
|
|
2531
|
+
);
|
|
2532
|
+
if (err) {
|
|
2533
|
+
return [
|
|
2534
|
+
fromConvexError(err),
|
|
2535
|
+
null
|
|
2536
|
+
];
|
|
2537
|
+
}
|
|
2538
|
+
if (!raw) {
|
|
2539
|
+
return [
|
|
2540
|
+
new CapxulError({
|
|
2541
|
+
code: "NOT_FOUND",
|
|
2542
|
+
message: `external_account ${input.externalAccountId} not found`
|
|
2543
|
+
}),
|
|
2544
|
+
null
|
|
2545
|
+
];
|
|
2546
|
+
}
|
|
2547
|
+
return [
|
|
2548
|
+
null,
|
|
2549
|
+
brandExternalAccount(
|
|
2550
|
+
raw
|
|
2551
|
+
)
|
|
2552
|
+
];
|
|
1239
2553
|
},
|
|
1240
|
-
|
|
2554
|
+
remove: async (input) => {
|
|
1241
2555
|
if (!config.data) {
|
|
1242
|
-
return stub(
|
|
2556
|
+
return stub(
|
|
2557
|
+
"organizations.externalAccounts.remove"
|
|
2558
|
+
);
|
|
1243
2559
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
2560
|
+
const [err] = await tryCatch(
|
|
2561
|
+
config.data.mutation(api.externalAccounts.mutations.removeOrg, {
|
|
2562
|
+
organizationId: input.organizationId,
|
|
2563
|
+
externalAccountId: input.externalAccountId
|
|
2564
|
+
})
|
|
2565
|
+
);
|
|
2566
|
+
if (err) {
|
|
2567
|
+
return [
|
|
2568
|
+
fromConvexError(err),
|
|
2569
|
+
null
|
|
2570
|
+
];
|
|
2571
|
+
}
|
|
2572
|
+
return [null, void 0];
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function createOrgSubAccountsClient(config) {
|
|
2577
|
+
return {
|
|
2578
|
+
create: async (input) => {
|
|
2579
|
+
if (!config.data) {
|
|
2580
|
+
return stub(
|
|
2581
|
+
"organizations.subAccounts.create"
|
|
1248
2582
|
);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
2583
|
+
}
|
|
2584
|
+
const [err, raw] = await tryCatch(
|
|
2585
|
+
config.data.mutation(api.subAccounts.mutations.create, {
|
|
2586
|
+
parent: {
|
|
2587
|
+
kind: "organization",
|
|
2588
|
+
id: input.organizationId
|
|
2589
|
+
},
|
|
2590
|
+
name: input.name,
|
|
2591
|
+
purpose: input.purpose
|
|
2592
|
+
})
|
|
2593
|
+
);
|
|
2594
|
+
if (err) {
|
|
2595
|
+
return [
|
|
2596
|
+
fromConvexError(err),
|
|
2597
|
+
null
|
|
2598
|
+
];
|
|
2599
|
+
}
|
|
2600
|
+
if (!raw) {
|
|
2601
|
+
return [
|
|
2602
|
+
new CapxulError({
|
|
2603
|
+
code: "NOT_FOUND",
|
|
2604
|
+
message: "sub_account creation returned no resource"
|
|
2605
|
+
}),
|
|
2606
|
+
null
|
|
2607
|
+
];
|
|
2608
|
+
}
|
|
2609
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2610
|
+
if (brandErr) {
|
|
1260
2611
|
return [
|
|
1261
|
-
|
|
2612
|
+
brandErr,
|
|
1262
2613
|
null
|
|
1263
2614
|
];
|
|
1264
2615
|
}
|
|
2616
|
+
return [null, branded];
|
|
1265
2617
|
},
|
|
1266
2618
|
list: async (input) => {
|
|
1267
2619
|
if (!config.data) {
|
|
1268
|
-
return stub(
|
|
1269
|
-
|
|
1270
|
-
try {
|
|
1271
|
-
const result = await config.data.query(
|
|
1272
|
-
api.withdrawals.queries.list,
|
|
1273
|
-
{
|
|
1274
|
-
limit: input?.limit,
|
|
1275
|
-
cursor: input?.cursor
|
|
1276
|
-
}
|
|
2620
|
+
return stub(
|
|
2621
|
+
"organizations.subAccounts.list"
|
|
1277
2622
|
);
|
|
1278
|
-
|
|
1279
|
-
|
|
2623
|
+
}
|
|
2624
|
+
const [err, rows] = await tryCatch(
|
|
2625
|
+
config.data.query(api.subAccounts.queries.listByOrganization, {
|
|
2626
|
+
organizationId: input.organizationId
|
|
2627
|
+
})
|
|
2628
|
+
);
|
|
2629
|
+
if (err) {
|
|
1280
2630
|
return [
|
|
1281
|
-
fromConvexError(
|
|
2631
|
+
fromConvexError(err),
|
|
1282
2632
|
null
|
|
1283
2633
|
];
|
|
1284
2634
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
return {
|
|
1290
|
-
// Slice 1 ships personal-scope only end-to-end; org-scope create
|
|
1291
|
-
// remains stubbed pending org-scoped backend mutation. List + retrieve
|
|
1292
|
-
// are wired through the org-aware query.
|
|
1293
|
-
create: async () => stub(
|
|
1294
|
-
"organizations.withdrawals.create"
|
|
1295
|
-
),
|
|
1296
|
-
retrieve: async (input) => {
|
|
1297
|
-
if (!config.data) {
|
|
1298
|
-
return stub(
|
|
1299
|
-
"organizations.withdrawals.retrieve"
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
try {
|
|
1303
|
-
const withdrawal = await config.data.query(
|
|
1304
|
-
api.withdrawals.queries.retrieve,
|
|
1305
|
-
{ withdrawalId: input.withdrawalId }
|
|
1306
|
-
);
|
|
1307
|
-
if (!withdrawal) {
|
|
2635
|
+
const branded = [];
|
|
2636
|
+
for (const row of rows) {
|
|
2637
|
+
const [brandErr, value] = tryBrandSubAccount(row);
|
|
2638
|
+
if (brandErr) {
|
|
1308
2639
|
return [
|
|
1309
|
-
|
|
1310
|
-
code: "NOT_FOUND",
|
|
1311
|
-
message: `withdrawal ${input.withdrawalId} not found`
|
|
1312
|
-
}),
|
|
2640
|
+
brandErr,
|
|
1313
2641
|
null
|
|
1314
2642
|
];
|
|
1315
2643
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
];
|
|
2644
|
+
branded.push(value);
|
|
2645
|
+
}
|
|
2646
|
+
return [
|
|
2647
|
+
null,
|
|
2648
|
+
{
|
|
2649
|
+
object: "list",
|
|
2650
|
+
data: branded,
|
|
2651
|
+
page: { hasMore: false }
|
|
1325
2652
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
2653
|
+
];
|
|
2654
|
+
},
|
|
2655
|
+
retrieve: async (input) => {
|
|
2656
|
+
if (!config.data) {
|
|
2657
|
+
return stub(
|
|
2658
|
+
"organizations.subAccounts.retrieve"
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
const [err, raw] = await tryCatch(
|
|
2662
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
2663
|
+
subAccountId: input.subAccountId
|
|
2664
|
+
})
|
|
2665
|
+
);
|
|
2666
|
+
if (err) {
|
|
1328
2667
|
return [
|
|
1329
|
-
fromConvexError(
|
|
2668
|
+
fromConvexError(err),
|
|
2669
|
+
null
|
|
2670
|
+
];
|
|
2671
|
+
}
|
|
2672
|
+
if (!raw) {
|
|
2673
|
+
return [
|
|
2674
|
+
new CapxulError({
|
|
2675
|
+
code: "NOT_FOUND",
|
|
2676
|
+
message: `sub_account ${input.subAccountId} not found`
|
|
2677
|
+
}),
|
|
2678
|
+
null
|
|
2679
|
+
];
|
|
2680
|
+
}
|
|
2681
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2682
|
+
if (brandErr) {
|
|
2683
|
+
return [
|
|
2684
|
+
brandErr,
|
|
1330
2685
|
null
|
|
1331
2686
|
];
|
|
1332
2687
|
}
|
|
2688
|
+
return [null, branded];
|
|
1333
2689
|
},
|
|
1334
|
-
|
|
2690
|
+
remove: async (input) => {
|
|
1335
2691
|
if (!config.data) {
|
|
1336
2692
|
return stub(
|
|
1337
|
-
"organizations.
|
|
2693
|
+
"organizations.subAccounts.remove"
|
|
1338
2694
|
);
|
|
1339
2695
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
cursor: input.cursor
|
|
1347
|
-
}
|
|
1348
|
-
);
|
|
1349
|
-
return [null, result];
|
|
1350
|
-
} catch (cause) {
|
|
2696
|
+
const [err, raw] = await tryCatch(
|
|
2697
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
2698
|
+
subAccountId: input.subAccountId
|
|
2699
|
+
})
|
|
2700
|
+
);
|
|
2701
|
+
if (err) {
|
|
1351
2702
|
return [
|
|
1352
|
-
fromConvexError(
|
|
2703
|
+
fromConvexError(err),
|
|
2704
|
+
null
|
|
2705
|
+
];
|
|
2706
|
+
}
|
|
2707
|
+
if (!raw) {
|
|
2708
|
+
return [
|
|
2709
|
+
new CapxulError({
|
|
2710
|
+
code: "NOT_FOUND",
|
|
2711
|
+
message: `sub_account ${input.subAccountId} not found`
|
|
2712
|
+
}),
|
|
2713
|
+
null
|
|
2714
|
+
];
|
|
2715
|
+
}
|
|
2716
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2717
|
+
if (brandErr) {
|
|
2718
|
+
return [
|
|
2719
|
+
brandErr,
|
|
1353
2720
|
null
|
|
1354
2721
|
];
|
|
1355
2722
|
}
|
|
2723
|
+
return [null, branded];
|
|
1356
2724
|
}
|
|
1357
2725
|
};
|
|
1358
2726
|
}
|
|
1359
|
-
async function bestEffortMarkFailed2(config, withdrawalId, error) {
|
|
1360
|
-
try {
|
|
1361
|
-
await config.data.mutation(api.withdrawals.mutations.markFailed, {
|
|
1362
|
-
withdrawalId,
|
|
1363
|
-
errorCode: error.code,
|
|
1364
|
-
errorMessage: error.message
|
|
1365
|
-
});
|
|
1366
|
-
} catch {
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
function mapCreateError2(error) {
|
|
1370
|
-
switch (error.code) {
|
|
1371
|
-
case "NOT_AUTHENTICATED":
|
|
1372
|
-
case "PERMISSION_DENIED":
|
|
1373
|
-
case "INVALID_INPUT":
|
|
1374
|
-
case "INSUFFICIENT_BALANCE":
|
|
1375
|
-
case "IDEMPOTENCY_CONFLICT":
|
|
1376
|
-
case "KYC_REQUIRED":
|
|
1377
|
-
case "POLICY_DENIED":
|
|
1378
|
-
case "RATE_LIMITED":
|
|
1379
|
-
case "NETWORK_ERROR":
|
|
1380
|
-
return error;
|
|
1381
|
-
default:
|
|
1382
|
-
return new CapxulError({
|
|
1383
|
-
code: "NETWORK_ERROR",
|
|
1384
|
-
message: error.message,
|
|
1385
|
-
cause: error,
|
|
1386
|
-
details: error.details,
|
|
1387
|
-
operationId: error.operationId,
|
|
1388
|
-
correlationId: error.correlationId,
|
|
1389
|
-
retryable: error.retryable
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
// src/core/webhook-endpoints.ts
|
|
1395
|
-
function createWebhookEndpointsClient() {
|
|
1396
|
-
return {
|
|
1397
|
-
create: async () => stub(
|
|
1398
|
-
"webhookEndpoints.create"
|
|
1399
|
-
),
|
|
1400
|
-
retrieve: async () => stub("webhookEndpoints.retrieve"),
|
|
1401
|
-
list: async () => stub("webhookEndpoints.list"),
|
|
1402
|
-
remove: async () => stub("webhookEndpoints.remove")
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
// src/core/webhook-events.ts
|
|
1407
|
-
function createWebhookEventsClient() {
|
|
1408
|
-
return {
|
|
1409
|
-
retrieve: async () => stub("webhookEvents.retrieve"),
|
|
1410
|
-
list: async () => stub("webhookEvents.list")
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// src/core/organizations.ts
|
|
1415
2727
|
function createOrganizationsClient(config = {}) {
|
|
1416
2728
|
return {
|
|
1417
2729
|
create: async () => stub("organizations.create"),
|
|
@@ -1461,24 +2773,8 @@ function createOrganizationsClient(config = {}) {
|
|
|
1461
2773
|
start: async () => stub("organizations.kybProfile.start"),
|
|
1462
2774
|
retrieve: async () => stub("organizations.kybProfile.retrieve")
|
|
1463
2775
|
},
|
|
1464
|
-
subAccounts:
|
|
1465
|
-
|
|
1466
|
-
list: async () => stub("organizations.subAccounts.list"),
|
|
1467
|
-
retrieve: async () => stub("organizations.subAccounts.retrieve"),
|
|
1468
|
-
remove: async () => stub("organizations.subAccounts.remove")
|
|
1469
|
-
},
|
|
1470
|
-
externalAccounts: {
|
|
1471
|
-
create: async () => stub(
|
|
1472
|
-
"organizations.externalAccounts.create"
|
|
1473
|
-
),
|
|
1474
|
-
list: async () => stub(
|
|
1475
|
-
"organizations.externalAccounts.list"
|
|
1476
|
-
),
|
|
1477
|
-
retrieve: async () => stub(
|
|
1478
|
-
"organizations.externalAccounts.retrieve"
|
|
1479
|
-
),
|
|
1480
|
-
remove: async () => stub("organizations.externalAccounts.remove")
|
|
1481
|
-
},
|
|
2776
|
+
subAccounts: createOrgSubAccountsClient(config),
|
|
2777
|
+
externalAccounts: createOrgExternalAccountsClient(config),
|
|
1482
2778
|
balanceLedger: {
|
|
1483
2779
|
list: async () => stub(
|
|
1484
2780
|
"organizations.balanceLedger.list"
|
|
@@ -1496,78 +2792,116 @@ function createOrganizationsClient(config = {}) {
|
|
|
1496
2792
|
};
|
|
1497
2793
|
}
|
|
1498
2794
|
|
|
1499
|
-
// src/core/
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
function
|
|
2795
|
+
// src/core/token-transfers.ts
|
|
2796
|
+
var toTokenTransferId = (raw) => {
|
|
2797
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
2798
|
+
throw new Error(
|
|
2799
|
+
`Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
return raw;
|
|
2803
|
+
};
|
|
2804
|
+
function brandRow(row) {
|
|
1509
2805
|
return {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
list: async () => stub("virtualAccounts.list"),
|
|
1513
|
-
remove: async () => stub("virtualAccounts.remove")
|
|
2806
|
+
...row,
|
|
2807
|
+
id: toTokenTransferId(row.id)
|
|
1514
2808
|
};
|
|
1515
2809
|
}
|
|
1516
|
-
|
|
1517
|
-
// src/core/virtual-cards.ts
|
|
1518
|
-
function createVirtualCardsClient() {
|
|
2810
|
+
function createTokenTransfersClient(config = {}) {
|
|
1519
2811
|
return {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
2812
|
+
list: async (input) => {
|
|
2813
|
+
if (!config.data) {
|
|
2814
|
+
return stub("tokenTransfers.list");
|
|
2815
|
+
}
|
|
2816
|
+
try {
|
|
2817
|
+
const raw = await config.data.query(
|
|
2818
|
+
api.tokenTransfers.queries.list,
|
|
2819
|
+
{
|
|
2820
|
+
limit: input?.limit,
|
|
2821
|
+
cursor: input?.cursor,
|
|
2822
|
+
direction: input?.direction
|
|
2823
|
+
}
|
|
2824
|
+
);
|
|
2825
|
+
if (!raw) {
|
|
2826
|
+
return [
|
|
2827
|
+
new CapxulError({
|
|
2828
|
+
code: "NOT_AUTHENTICATED",
|
|
2829
|
+
message: "tokenTransfers.list requires an authenticated session."
|
|
2830
|
+
}),
|
|
2831
|
+
null
|
|
2832
|
+
];
|
|
2833
|
+
}
|
|
2834
|
+
return [
|
|
2835
|
+
null,
|
|
2836
|
+
{
|
|
2837
|
+
object: "list",
|
|
2838
|
+
data: raw.items.map(brandRow),
|
|
2839
|
+
page: {
|
|
2840
|
+
hasMore: raw.hasMore,
|
|
2841
|
+
nextCursor: raw.nextCursor
|
|
2842
|
+
},
|
|
2843
|
+
displayCurrency: raw.displayCurrency
|
|
2844
|
+
}
|
|
2845
|
+
];
|
|
2846
|
+
} catch (cause) {
|
|
2847
|
+
return [fromConvexError(cause), null];
|
|
2848
|
+
}
|
|
2849
|
+
},
|
|
2850
|
+
retrieve: async (input) => {
|
|
2851
|
+
if (!config.data) {
|
|
2852
|
+
return stub("tokenTransfers.retrieve");
|
|
2853
|
+
}
|
|
2854
|
+
try {
|
|
2855
|
+
const raw = await config.data.query(
|
|
2856
|
+
api.tokenTransfers.queries.getByTxLogIndex,
|
|
2857
|
+
{
|
|
2858
|
+
txHash: input.txHash,
|
|
2859
|
+
logIndex: input.logIndex,
|
|
2860
|
+
chainId: input.chainId
|
|
2861
|
+
}
|
|
2862
|
+
);
|
|
2863
|
+
if (!raw) {
|
|
2864
|
+
return [
|
|
2865
|
+
new CapxulError({
|
|
2866
|
+
code: "NOT_FOUND",
|
|
2867
|
+
message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
|
|
2868
|
+
details: {
|
|
2869
|
+
txHash: input.txHash,
|
|
2870
|
+
logIndex: input.logIndex,
|
|
2871
|
+
chainId: input.chainId
|
|
2872
|
+
}
|
|
2873
|
+
}),
|
|
2874
|
+
null
|
|
2875
|
+
];
|
|
2876
|
+
}
|
|
2877
|
+
return [null, brandRow(raw)];
|
|
2878
|
+
} catch (cause) {
|
|
2879
|
+
return [fromConvexError(cause), null];
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
1526
2882
|
};
|
|
1527
2883
|
}
|
|
1528
|
-
|
|
1529
|
-
//
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
if (typeof value === "string") return value;
|
|
1550
|
-
try {
|
|
1551
|
-
return JSON.stringify(value);
|
|
1552
|
-
} catch {
|
|
1553
|
-
return String(value);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
function track(...args) {
|
|
1557
|
-
const [name, props] = args;
|
|
1558
|
-
debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
|
|
1559
|
-
}
|
|
1560
|
-
function formatDebugValue2(value) {
|
|
1561
|
-
if (value === void 0 || value === "") return "";
|
|
1562
|
-
if (typeof value === "string") return value;
|
|
1563
|
-
try {
|
|
1564
|
-
return JSON.stringify(value);
|
|
1565
|
-
} catch {
|
|
1566
|
-
return String(value);
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
function identify(userId, traits) {
|
|
1570
|
-
debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
|
|
2884
|
+
|
|
2885
|
+
// src/core/virtual-accounts.ts
|
|
2886
|
+
function createVirtualAccountsClient() {
|
|
2887
|
+
return {
|
|
2888
|
+
create: async () => stub("virtualAccounts.create"),
|
|
2889
|
+
retrieve: async () => stub("virtualAccounts.retrieve"),
|
|
2890
|
+
list: async () => stub("virtualAccounts.list"),
|
|
2891
|
+
remove: async () => stub("virtualAccounts.remove")
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// src/core/virtual-cards.ts
|
|
2896
|
+
function createVirtualCardsClient() {
|
|
2897
|
+
return {
|
|
2898
|
+
create: async () => stub("virtualCards.create"),
|
|
2899
|
+
retrieve: async () => stub("virtualCards.retrieve"),
|
|
2900
|
+
list: async () => stub("virtualCards.list"),
|
|
2901
|
+
freeze: async () => stub("virtualCards.freeze"),
|
|
2902
|
+
unfreeze: async () => stub("virtualCards.unfreeze"),
|
|
2903
|
+
cancel: async () => stub("virtualCards.cancel")
|
|
2904
|
+
};
|
|
1571
2905
|
}
|
|
1572
2906
|
function createAuthFlowMachine(client) {
|
|
1573
2907
|
return setup({
|
|
@@ -1588,7 +2922,7 @@ function createAuthFlowMachine(client) {
|
|
|
1588
2922
|
}),
|
|
1589
2923
|
verifyOtp: fromPromise(
|
|
1590
2924
|
async ({ input, signal }) => {
|
|
1591
|
-
const [error,
|
|
2925
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
1592
2926
|
{
|
|
1593
2927
|
email: input.email,
|
|
1594
2928
|
otp: input.code
|
|
@@ -1596,7 +2930,14 @@ function createAuthFlowMachine(client) {
|
|
|
1596
2930
|
{ signal }
|
|
1597
2931
|
);
|
|
1598
2932
|
if (error) throw error;
|
|
1599
|
-
|
|
2933
|
+
if (result.kind === "bootstrap_required") {
|
|
2934
|
+
throw new CapxulError({
|
|
2935
|
+
code: "ACTION_REQUIRED",
|
|
2936
|
+
message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
|
|
2937
|
+
details: { reason: result.reason }
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
return result.session;
|
|
1600
2941
|
}
|
|
1601
2942
|
),
|
|
1602
2943
|
signOut: fromPromise(async () => {
|
|
@@ -1844,23 +3185,345 @@ function emailDomain(email) {
|
|
|
1844
3185
|
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
1845
3186
|
return domain || "unknown";
|
|
1846
3187
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
3188
|
+
var initialContext = {
|
|
3189
|
+
email: null,
|
|
3190
|
+
code: null,
|
|
3191
|
+
username: null,
|
|
3192
|
+
signerProvider: null,
|
|
3193
|
+
bootstrapToken: null,
|
|
3194
|
+
bootstrapReason: null,
|
|
3195
|
+
session: null,
|
|
3196
|
+
account: null,
|
|
3197
|
+
safe: null,
|
|
3198
|
+
error: null
|
|
3199
|
+
};
|
|
3200
|
+
function createAuthBootstrapFlowMachine(client) {
|
|
3201
|
+
return setup({
|
|
3202
|
+
types: {},
|
|
3203
|
+
actors: {
|
|
3204
|
+
sendOtp: fromPromise(async ({ input, signal }) => {
|
|
3205
|
+
const [error] = await client.auth.sendOtp(
|
|
3206
|
+
{ email: input.email },
|
|
3207
|
+
{ signal }
|
|
3208
|
+
);
|
|
3209
|
+
if (error) throw error;
|
|
3210
|
+
}),
|
|
3211
|
+
verifyOtp: fromPromise(
|
|
3212
|
+
async ({ input, signal }) => {
|
|
3213
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
3214
|
+
{ email: input.email, otp: input.code },
|
|
3215
|
+
{ signal }
|
|
3216
|
+
);
|
|
3217
|
+
if (error) throw error;
|
|
3218
|
+
return result;
|
|
3219
|
+
}
|
|
3220
|
+
),
|
|
3221
|
+
completeBootstrap: fromPromise(async ({ input }) => {
|
|
3222
|
+
const [error, result] = await client.auth.completeBootstrap(input);
|
|
3223
|
+
if (error) throw error;
|
|
3224
|
+
return result;
|
|
3225
|
+
}),
|
|
3226
|
+
signOut: fromPromise(async () => {
|
|
3227
|
+
const [error] = await client.auth.signOut();
|
|
3228
|
+
if (error) throw error;
|
|
3229
|
+
})
|
|
3230
|
+
},
|
|
3231
|
+
actions: {
|
|
3232
|
+
trackOtpRequested: ({ context }) => {
|
|
3233
|
+
if (!context.email) return;
|
|
3234
|
+
track("auth_otp_requested", {
|
|
3235
|
+
email_domain: emailDomain2(context.email)
|
|
3236
|
+
});
|
|
3237
|
+
},
|
|
3238
|
+
trackFailed: ({ event }) => {
|
|
3239
|
+
track("auth_failed", {
|
|
3240
|
+
auth_type: "email_otp",
|
|
3241
|
+
reason: errorFromEvent2(event).code
|
|
3242
|
+
});
|
|
3243
|
+
},
|
|
3244
|
+
trackTimeoutFailed: () => {
|
|
3245
|
+
track("auth_failed", {
|
|
3246
|
+
auth_type: "email_otp",
|
|
3247
|
+
reason: "timeout"
|
|
3248
|
+
});
|
|
3249
|
+
},
|
|
3250
|
+
trackVerified: () => {
|
|
3251
|
+
track("auth_verified", { auth_type: "email_otp" });
|
|
3252
|
+
},
|
|
3253
|
+
trackBootstrapRequired: ({ context }) => {
|
|
3254
|
+
track("auth_verified", {
|
|
3255
|
+
auth_type: "email_otp",
|
|
3256
|
+
auth_mode: context.bootstrapReason ?? "bootstrap_required"
|
|
3257
|
+
});
|
|
3258
|
+
},
|
|
3259
|
+
identifyAndTrack: ({ context }) => {
|
|
3260
|
+
if (!context.session) return;
|
|
3261
|
+
identify(context.session.authUserId, {
|
|
3262
|
+
email_domain: emailDomain2(context.session.email)
|
|
3263
|
+
});
|
|
3264
|
+
track("auth_identified", {
|
|
3265
|
+
email_domain: emailDomain2(context.session.email)
|
|
3266
|
+
});
|
|
3267
|
+
},
|
|
3268
|
+
trackSignedOut: () => {
|
|
3269
|
+
track("auth_signed_out");
|
|
3270
|
+
}
|
|
1856
3271
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
3272
|
+
}).createMachine({
|
|
3273
|
+
id: "authBootstrap",
|
|
3274
|
+
initial: "email",
|
|
3275
|
+
context: initialContext,
|
|
3276
|
+
states: {
|
|
3277
|
+
email: {
|
|
3278
|
+
on: {
|
|
3279
|
+
ENTER_EMAIL: {
|
|
3280
|
+
actions: assign({
|
|
3281
|
+
email: ({ event }) => event.email,
|
|
3282
|
+
error: () => null
|
|
3283
|
+
})
|
|
3284
|
+
},
|
|
3285
|
+
REQUEST_OTP: { target: "sending_otp" }
|
|
3286
|
+
}
|
|
3287
|
+
},
|
|
3288
|
+
sending_otp: {
|
|
3289
|
+
invoke: {
|
|
3290
|
+
src: "sendOtp",
|
|
3291
|
+
input: ({ context }) => ({ email: requireEmail2(context) }),
|
|
3292
|
+
onDone: { target: "otp_requested", actions: "trackOtpRequested" },
|
|
3293
|
+
onError: {
|
|
3294
|
+
target: "otp_requested",
|
|
3295
|
+
actions: [
|
|
3296
|
+
assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3297
|
+
"trackFailed"
|
|
3298
|
+
]
|
|
3299
|
+
}
|
|
3300
|
+
},
|
|
3301
|
+
after: {
|
|
3302
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3303
|
+
target: "otp_requested",
|
|
3304
|
+
actions: [
|
|
3305
|
+
assign({ error: () => timeoutError2("sending_otp") }),
|
|
3306
|
+
"trackTimeoutFailed"
|
|
3307
|
+
]
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
},
|
|
3311
|
+
otp_requested: {
|
|
3312
|
+
on: {
|
|
3313
|
+
ENTER_OTP: {
|
|
3314
|
+
actions: assign({
|
|
3315
|
+
code: ({ event }) => event.code,
|
|
3316
|
+
error: () => null
|
|
3317
|
+
})
|
|
3318
|
+
},
|
|
3319
|
+
VERIFY_OTP: { target: "verifying_otp" },
|
|
3320
|
+
BACK: { target: "email" },
|
|
3321
|
+
RESET: { target: "email", actions: assign(() => initialContext) }
|
|
3322
|
+
}
|
|
3323
|
+
},
|
|
3324
|
+
verifying_otp: {
|
|
3325
|
+
invoke: {
|
|
3326
|
+
src: "verifyOtp",
|
|
3327
|
+
input: ({ context }) => ({
|
|
3328
|
+
email: requireEmail2(context),
|
|
3329
|
+
code: requireCode(context)
|
|
3330
|
+
}),
|
|
3331
|
+
onDone: [
|
|
3332
|
+
{
|
|
3333
|
+
guard: ({ event }) => event.output.kind === "existing_member",
|
|
3334
|
+
target: "authenticated",
|
|
3335
|
+
actions: [
|
|
3336
|
+
assign({
|
|
3337
|
+
session: ({ event }) => event.output.session,
|
|
3338
|
+
account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
|
|
3339
|
+
username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
|
|
3340
|
+
safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
|
|
3341
|
+
email: () => null,
|
|
3342
|
+
error: () => null
|
|
3343
|
+
}),
|
|
3344
|
+
"trackVerified",
|
|
3345
|
+
"identifyAndTrack"
|
|
3346
|
+
]
|
|
3347
|
+
},
|
|
3348
|
+
{
|
|
3349
|
+
target: "bootstrap_required",
|
|
3350
|
+
actions: [
|
|
3351
|
+
assign({
|
|
3352
|
+
session: ({ event }) => event.output.session,
|
|
3353
|
+
bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
|
|
3354
|
+
bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
|
|
3355
|
+
username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
|
|
3356
|
+
email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
|
|
3357
|
+
error: () => null
|
|
3358
|
+
}),
|
|
3359
|
+
"trackVerified",
|
|
3360
|
+
"trackBootstrapRequired"
|
|
3361
|
+
]
|
|
3362
|
+
}
|
|
3363
|
+
],
|
|
3364
|
+
onError: {
|
|
3365
|
+
target: "otp_requested",
|
|
3366
|
+
actions: [
|
|
3367
|
+
assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3368
|
+
"trackFailed"
|
|
3369
|
+
]
|
|
3370
|
+
}
|
|
3371
|
+
},
|
|
3372
|
+
after: {
|
|
3373
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3374
|
+
target: "otp_requested",
|
|
3375
|
+
actions: [
|
|
3376
|
+
assign({ error: () => timeoutError2("verifying_otp") }),
|
|
3377
|
+
"trackTimeoutFailed"
|
|
3378
|
+
]
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
},
|
|
3382
|
+
bootstrap_required: {
|
|
3383
|
+
on: {
|
|
3384
|
+
ENTER_USERNAME: {
|
|
3385
|
+
actions: assign({
|
|
3386
|
+
username: ({ event }) => event.username,
|
|
3387
|
+
error: () => null
|
|
3388
|
+
})
|
|
3389
|
+
},
|
|
3390
|
+
ENTER_SIGNER_PROVIDER: {
|
|
3391
|
+
actions: assign({
|
|
3392
|
+
signerProvider: ({ event }) => event.signerProvider,
|
|
3393
|
+
error: () => null
|
|
3394
|
+
})
|
|
3395
|
+
},
|
|
3396
|
+
COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
|
|
3397
|
+
BACK: { target: "otp_requested" },
|
|
3398
|
+
RESET: { target: "email", actions: assign(() => initialContext) }
|
|
3399
|
+
}
|
|
3400
|
+
},
|
|
3401
|
+
completing_bootstrap: {
|
|
3402
|
+
invoke: {
|
|
3403
|
+
src: "completeBootstrap",
|
|
3404
|
+
input: ({ context }) => ({
|
|
3405
|
+
bootstrapToken: requireBootstrapToken(context),
|
|
3406
|
+
username: requireUsername(context),
|
|
3407
|
+
signerProvider: requireSignerProvider(context)
|
|
3408
|
+
}),
|
|
3409
|
+
onDone: {
|
|
3410
|
+
target: "authenticated",
|
|
3411
|
+
actions: [
|
|
3412
|
+
assign({
|
|
3413
|
+
session: ({ event }) => event.output.session,
|
|
3414
|
+
account: ({ event }) => event.output.account,
|
|
3415
|
+
username: ({ event }) => event.output.username,
|
|
3416
|
+
safe: ({ event }) => event.output.safe,
|
|
3417
|
+
bootstrapToken: () => null,
|
|
3418
|
+
bootstrapReason: () => null,
|
|
3419
|
+
signerProvider: () => null,
|
|
3420
|
+
email: () => null,
|
|
3421
|
+
error: () => null
|
|
3422
|
+
}),
|
|
3423
|
+
"identifyAndTrack"
|
|
3424
|
+
]
|
|
3425
|
+
},
|
|
3426
|
+
onError: {
|
|
3427
|
+
target: "bootstrap_required",
|
|
3428
|
+
actions: [
|
|
3429
|
+
assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3430
|
+
"trackFailed"
|
|
3431
|
+
]
|
|
3432
|
+
}
|
|
3433
|
+
},
|
|
3434
|
+
after: {
|
|
3435
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3436
|
+
target: "bootstrap_required",
|
|
3437
|
+
actions: [
|
|
3438
|
+
assign({ error: () => timeoutError2("completing_bootstrap") }),
|
|
3439
|
+
"trackTimeoutFailed"
|
|
3440
|
+
]
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
},
|
|
3444
|
+
authenticated: {
|
|
3445
|
+
on: {
|
|
3446
|
+
SIGN_OUT: { target: "signing_out" }
|
|
3447
|
+
}
|
|
3448
|
+
},
|
|
3449
|
+
signing_out: {
|
|
3450
|
+
invoke: {
|
|
3451
|
+
src: "signOut",
|
|
3452
|
+
onDone: {
|
|
3453
|
+
target: "email",
|
|
3454
|
+
actions: [
|
|
3455
|
+
assign(() => initialContext),
|
|
3456
|
+
"trackSignedOut"
|
|
3457
|
+
]
|
|
3458
|
+
},
|
|
3459
|
+
onError: {
|
|
3460
|
+
target: "error",
|
|
3461
|
+
actions: assign({ error: ({ event }) => errorFromEvent2(event) })
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
},
|
|
3465
|
+
error: {
|
|
3466
|
+
on: {
|
|
3467
|
+
RESET: { target: "email", actions: assign(() => initialContext) }
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
function requireEmail2(context) {
|
|
3474
|
+
if (!context.email) {
|
|
3475
|
+
throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
|
|
3476
|
+
}
|
|
3477
|
+
return context.email;
|
|
3478
|
+
}
|
|
3479
|
+
function requireCode(context) {
|
|
3480
|
+
if (!context.code) {
|
|
3481
|
+
throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
|
|
3482
|
+
}
|
|
3483
|
+
return context.code;
|
|
3484
|
+
}
|
|
3485
|
+
function requireBootstrapToken(context) {
|
|
3486
|
+
if (!context.bootstrapToken) {
|
|
3487
|
+
throw Errors.invalidInput(
|
|
3488
|
+
"bootstrapToken",
|
|
3489
|
+
"Auth bootstrap requires a continuation token."
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
return context.bootstrapToken;
|
|
3493
|
+
}
|
|
3494
|
+
function requireUsername(context) {
|
|
3495
|
+
if (!context.username) {
|
|
3496
|
+
throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
|
|
3497
|
+
}
|
|
3498
|
+
return context.username;
|
|
3499
|
+
}
|
|
3500
|
+
function requireSignerProvider(context) {
|
|
3501
|
+
if (!context.signerProvider) {
|
|
3502
|
+
throw Errors.invalidInput(
|
|
3503
|
+
"signerProvider",
|
|
3504
|
+
"Auth bootstrap requires a signer provider."
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
return context.signerProvider;
|
|
3508
|
+
}
|
|
3509
|
+
function errorFromEvent2(event) {
|
|
3510
|
+
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
3511
|
+
if (cause instanceof CapxulError || cause instanceof CapxulError2) {
|
|
3512
|
+
return cause;
|
|
3513
|
+
}
|
|
3514
|
+
return Errors.providerError("auth", "bootstrap", cause);
|
|
3515
|
+
}
|
|
3516
|
+
function timeoutError2(state) {
|
|
3517
|
+
return Errors.providerError(
|
|
3518
|
+
"auth",
|
|
3519
|
+
"bootstrap",
|
|
3520
|
+
new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
function emailDomain2(email) {
|
|
3524
|
+
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
3525
|
+
return domain || "unknown";
|
|
1859
3526
|
}
|
|
1860
|
-
var toOperationId = makePrefixedIdConstructor(
|
|
1861
|
-
"op",
|
|
1862
|
-
"operationId"
|
|
1863
|
-
);
|
|
1864
3527
|
function createProvisioningMachine(client) {
|
|
1865
3528
|
return setup({
|
|
1866
3529
|
types: {},
|
|
@@ -1933,13 +3596,13 @@ function createProvisioningMachine(client) {
|
|
|
1933
3596
|
},
|
|
1934
3597
|
onError: {
|
|
1935
3598
|
target: "error",
|
|
1936
|
-
actions: assign({ error: ({ event }) =>
|
|
3599
|
+
actions: assign({ error: ({ event }) => errorFromEvent3(event) })
|
|
1937
3600
|
}
|
|
1938
3601
|
},
|
|
1939
3602
|
after: {
|
|
1940
3603
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
1941
3604
|
target: "error",
|
|
1942
|
-
actions: assign({ error: () =>
|
|
3605
|
+
actions: assign({ error: () => timeoutError3() })
|
|
1943
3606
|
}
|
|
1944
3607
|
}
|
|
1945
3608
|
},
|
|
@@ -1957,7 +3620,7 @@ function createProvisioningMachine(client) {
|
|
|
1957
3620
|
* this payload on its `onDone` transition and branches via guards
|
|
1958
3621
|
* on `event.output.error`.
|
|
1959
3622
|
*/
|
|
1960
|
-
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error:
|
|
3623
|
+
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
|
|
1961
3624
|
});
|
|
1962
3625
|
}
|
|
1963
3626
|
function requireProvisionInput(context) {
|
|
@@ -1969,13 +3632,13 @@ function requireProvisionInput(context) {
|
|
|
1969
3632
|
}
|
|
1970
3633
|
return context.input;
|
|
1971
3634
|
}
|
|
1972
|
-
function
|
|
3635
|
+
function errorFromEvent3(event) {
|
|
1973
3636
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
1974
3637
|
if (cause instanceof CapxulError) return cause;
|
|
1975
3638
|
if (cause instanceof CapxulError2) return cause;
|
|
1976
3639
|
return Errors.providerError("provisioning", "flow", cause);
|
|
1977
3640
|
}
|
|
1978
|
-
function
|
|
3641
|
+
function timeoutError3() {
|
|
1979
3642
|
return Errors.providerError(
|
|
1980
3643
|
"provisioning",
|
|
1981
3644
|
"flow",
|
|
@@ -2068,7 +3731,7 @@ function createOnboardingFlowMachine(client) {
|
|
|
2068
3731
|
error: ({ event }) => extractChildErrorOrFallback(event)
|
|
2069
3732
|
}),
|
|
2070
3733
|
assignChildThrown: assign({
|
|
2071
|
-
error: ({ event }) =>
|
|
3734
|
+
error: ({ event }) => errorFromEvent4(event)
|
|
2072
3735
|
}),
|
|
2073
3736
|
assignAccountFromChild: assign({
|
|
2074
3737
|
account: ({ event }) => extractChildAccountOrNull(event)
|
|
@@ -2230,7 +3893,7 @@ function extractChildAccountOrNull(event) {
|
|
|
2230
3893
|
if (output && "account" in output && output.account) return output.account;
|
|
2231
3894
|
return null;
|
|
2232
3895
|
}
|
|
2233
|
-
function
|
|
3896
|
+
function errorFromEvent4(event) {
|
|
2234
3897
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2235
3898
|
if (cause instanceof CapxulError) return cause;
|
|
2236
3899
|
if (cause instanceof CapxulError2) return cause;
|
|
@@ -2247,12 +3910,13 @@ function createCapxulClient(config = {}) {
|
|
|
2247
3910
|
organizations: createOrganizationsClient(config),
|
|
2248
3911
|
payments: createPaymentsClient(config),
|
|
2249
3912
|
transfers: createTransfersClient(),
|
|
3913
|
+
tokenTransfers: createTokenTransfersClient(config),
|
|
2250
3914
|
withdrawals: createWithdrawalsClient(config),
|
|
2251
3915
|
documents: createDocumentsClient(),
|
|
2252
|
-
subAccounts: createSubAccountsClient(),
|
|
3916
|
+
subAccounts: createSubAccountsClient(config),
|
|
2253
3917
|
virtualAccounts: createVirtualAccountsClient(),
|
|
2254
3918
|
virtualCards: createVirtualCardsClient(),
|
|
2255
|
-
externalAccounts: createExternalAccountsClient(),
|
|
3919
|
+
externalAccounts: createExternalAccountsClient(config),
|
|
2256
3920
|
operations: createOperationsClient(config),
|
|
2257
3921
|
webhookEndpoints: createWebhookEndpointsClient(),
|
|
2258
3922
|
webhookEvents: createWebhookEventsClient(),
|
|
@@ -2261,6 +3925,7 @@ function createCapxulClient(config = {}) {
|
|
|
2261
3925
|
const client = clientWithoutFlows;
|
|
2262
3926
|
client.flows = {
|
|
2263
3927
|
auth: () => createAuthFlowMachine(client),
|
|
3928
|
+
authBootstrap: () => createAuthBootstrapFlowMachine(client),
|
|
2264
3929
|
onboarding: () => createOnboardingFlowMachine(client),
|
|
2265
3930
|
provisioning: () => createProvisioningMachine(client)
|
|
2266
3931
|
};
|