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