@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/index.cjs
CHANGED
|
@@ -70,12 +70,775 @@ function fromConvexError(error) {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// ../observability/src/try-catch.ts
|
|
74
|
+
async function tryCatch(promise) {
|
|
75
|
+
try {
|
|
76
|
+
return [null, await promise];
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return [e instanceof Error ? e : new Error(String(e)), null];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ../observability/src/debug-log.ts
|
|
83
|
+
function isDevelopmentBuild() {
|
|
84
|
+
if (typeof process === "undefined") {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
|
|
88
|
+
}
|
|
89
|
+
function debugLog(line) {
|
|
90
|
+
if (!isDevelopmentBuild()) return;
|
|
91
|
+
if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
|
|
92
|
+
console.info(line);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
|
|
96
|
+
process.stderr.write(`${line}
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function formatDebugValue(value) {
|
|
101
|
+
if (value === void 0 || value === "") return "";
|
|
102
|
+
if (typeof value === "string") return value;
|
|
103
|
+
try {
|
|
104
|
+
return JSON.stringify(value);
|
|
105
|
+
} catch {
|
|
106
|
+
return String(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function track(...args) {
|
|
110
|
+
const [name, props] = args;
|
|
111
|
+
debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
|
|
112
|
+
}
|
|
113
|
+
function formatDebugValue2(value) {
|
|
114
|
+
if (value === void 0 || value === "") return "";
|
|
115
|
+
if (typeof value === "string") return value;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
} catch {
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function identify(userId, traits) {
|
|
123
|
+
debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ../platform-kernel/src/ids.ts
|
|
127
|
+
function makePrefixedIdConstructor(prefix, fieldName) {
|
|
128
|
+
const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
|
|
129
|
+
return (raw) => {
|
|
130
|
+
if (typeof raw !== "string" || !re.test(raw)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return raw;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
var toAccountId = makePrefixedIdConstructor(
|
|
139
|
+
"acct",
|
|
140
|
+
"accountId"
|
|
141
|
+
);
|
|
142
|
+
var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
|
|
143
|
+
var toMemberId = makePrefixedIdConstructor(
|
|
144
|
+
"mem",
|
|
145
|
+
"memberId"
|
|
146
|
+
);
|
|
147
|
+
var toSafeId = makePrefixedIdConstructor(
|
|
148
|
+
"safe",
|
|
149
|
+
"safeId"
|
|
150
|
+
);
|
|
151
|
+
var toTreasuryId = makePrefixedIdConstructor(
|
|
152
|
+
"try",
|
|
153
|
+
"treasuryId"
|
|
154
|
+
);
|
|
155
|
+
var toApiKeyId = makePrefixedIdConstructor(
|
|
156
|
+
"ak",
|
|
157
|
+
"apiKeyId"
|
|
158
|
+
);
|
|
159
|
+
var toOperationId = makePrefixedIdConstructor(
|
|
160
|
+
"op",
|
|
161
|
+
"operationId"
|
|
162
|
+
);
|
|
163
|
+
var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
|
|
164
|
+
var toKycProfileId = makePrefixedIdConstructor(
|
|
165
|
+
"kyc",
|
|
166
|
+
"kycProfileId"
|
|
167
|
+
);
|
|
168
|
+
var toKybProfileId = makePrefixedIdConstructor(
|
|
169
|
+
"kyb",
|
|
170
|
+
"kybProfileId"
|
|
171
|
+
);
|
|
172
|
+
var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
|
|
173
|
+
var toPaymentId = makePrefixedIdConstructor(
|
|
174
|
+
"pay",
|
|
175
|
+
"paymentId"
|
|
176
|
+
);
|
|
177
|
+
var toWithdrawalId = makePrefixedIdConstructor(
|
|
178
|
+
"wd",
|
|
179
|
+
"withdrawalId"
|
|
180
|
+
);
|
|
181
|
+
var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
|
|
182
|
+
var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
|
|
183
|
+
var toSubAccountId = makePrefixedIdConstructor(
|
|
184
|
+
"sub",
|
|
185
|
+
"subAccountId"
|
|
186
|
+
);
|
|
187
|
+
var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
|
|
188
|
+
var toVirtualCardId = makePrefixedIdConstructor(
|
|
189
|
+
"vc",
|
|
190
|
+
"virtualCardId"
|
|
191
|
+
);
|
|
192
|
+
var toTransferId = makePrefixedIdConstructor(
|
|
193
|
+
"txfr",
|
|
194
|
+
"transferId"
|
|
195
|
+
);
|
|
196
|
+
var toDocumentId = makePrefixedIdConstructor(
|
|
197
|
+
"doc",
|
|
198
|
+
"documentId"
|
|
199
|
+
);
|
|
200
|
+
var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
|
|
201
|
+
|
|
202
|
+
// ../platform-kernel/src/value-objects.ts
|
|
203
|
+
var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
|
|
204
|
+
function toPhoneNumber(raw) {
|
|
205
|
+
if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return raw;
|
|
211
|
+
}
|
|
212
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
213
|
+
function toEmail(raw) {
|
|
214
|
+
if (typeof raw !== "string") {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Invalid email: expected string, got ${String(raw)}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const lowered = raw.trim().toLowerCase();
|
|
220
|
+
if (!EMAIL_RE.test(lowered)) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Invalid email: expected local@domain.tld, got ${String(raw)}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return lowered;
|
|
226
|
+
}
|
|
227
|
+
var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
|
|
228
|
+
function toUsername(raw) {
|
|
229
|
+
if (typeof raw !== "string") {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Invalid username: expected string, got ${String(raw)}`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const lowered = raw.toLowerCase();
|
|
235
|
+
if (!USERNAME_RE.test(lowered)) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return lowered;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/core/external-accounts.ts
|
|
244
|
+
function brandExternalAccount(raw) {
|
|
245
|
+
return {
|
|
246
|
+
...raw,
|
|
247
|
+
id: toExternalAccountId(raw.id),
|
|
248
|
+
operation: {
|
|
249
|
+
id: toOperationId(raw.operation.id),
|
|
250
|
+
status: raw.operation.status,
|
|
251
|
+
correlationId: toCorrelationId(raw.operation.correlationId)
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function createExternalAccountsClient(config = {}) {
|
|
256
|
+
return {
|
|
257
|
+
retrieve: async (externalAccountId) => {
|
|
258
|
+
if (!config.data) {
|
|
259
|
+
return stub(
|
|
260
|
+
"externalAccounts.retrieve"
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const [err, raw] = await tryCatch(
|
|
264
|
+
config.data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
265
|
+
externalAccountId
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
if (err) {
|
|
269
|
+
return [
|
|
270
|
+
fromConvexError(err),
|
|
271
|
+
null
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
if (!raw) {
|
|
275
|
+
return [
|
|
276
|
+
new CapxulError({
|
|
277
|
+
code: "NOT_FOUND",
|
|
278
|
+
message: `external_account ${externalAccountId} not found`
|
|
279
|
+
}),
|
|
280
|
+
null
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
return [null, brandExternalAccount(raw)];
|
|
284
|
+
},
|
|
285
|
+
remove: async (externalAccountId) => {
|
|
286
|
+
if (!config.data) {
|
|
287
|
+
return stub("externalAccounts.remove");
|
|
288
|
+
}
|
|
289
|
+
const [err] = await tryCatch(
|
|
290
|
+
config.data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
291
|
+
externalAccountId
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
if (err) {
|
|
295
|
+
return [
|
|
296
|
+
fromConvexError(err),
|
|
297
|
+
null
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
return [null, void 0];
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/core/sub-accounts.ts
|
|
306
|
+
function malformedWireError(reason, raw) {
|
|
307
|
+
return new CapxulError({
|
|
308
|
+
code: "PROVIDER_ERROR",
|
|
309
|
+
message: `convex brandSubAccount failed: ${reason}`,
|
|
310
|
+
details: {
|
|
311
|
+
provider: "convex",
|
|
312
|
+
operation: "brandSubAccount",
|
|
313
|
+
reason,
|
|
314
|
+
// PII discipline (`.claude/rules/posthog.md`): user-authored
|
|
315
|
+
// strings on the wire (`name`, `purpose`) are customer-confidential
|
|
316
|
+
// — sub-account names like "Q3 Acquisition Reserve" or
|
|
317
|
+
// "Vendor ABC payments" must not flow into telemetry. We emit a
|
|
318
|
+
// structural keys-only sample via a strict ALLOWLIST so any future
|
|
319
|
+
// wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
|
|
320
|
+
// by construction rather than leaked through a denylist gap.
|
|
321
|
+
sample: safeSampleShape(raw)
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function safeSampleShape(raw) {
|
|
326
|
+
if (raw === null || typeof raw !== "object") {
|
|
327
|
+
return { type: typeof raw };
|
|
328
|
+
}
|
|
329
|
+
const r = raw;
|
|
330
|
+
const balance = r.balance;
|
|
331
|
+
return {
|
|
332
|
+
object: typeof r.object === "string" ? r.object : typeof r.object,
|
|
333
|
+
idPresent: typeof r.id === "string" && r.id.length > 0,
|
|
334
|
+
// First 4 chars only — enough to distinguish "sub_" prefixed IDs
|
|
335
|
+
// from accidental other resource IDs without leaking the full ID.
|
|
336
|
+
idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
|
|
337
|
+
parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
|
|
338
|
+
status: r.status,
|
|
339
|
+
hasName: typeof r.name === "string",
|
|
340
|
+
hasPurpose: r.purpose !== void 0,
|
|
341
|
+
balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
|
|
342
|
+
createdAtType: typeof r.createdAt,
|
|
343
|
+
updatedAtType: typeof r.updatedAt
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function isMoneyShape(v) {
|
|
347
|
+
if (typeof v !== "object" || v === null) return false;
|
|
348
|
+
const m = v;
|
|
349
|
+
return typeof m.value === "string" && typeof m.currency === "string";
|
|
350
|
+
}
|
|
351
|
+
function isParentShape(v) {
|
|
352
|
+
if (typeof v !== "object" || v === null) return false;
|
|
353
|
+
const p = v;
|
|
354
|
+
return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
|
|
355
|
+
}
|
|
356
|
+
function isFiniteNonNegativeInteger(v) {
|
|
357
|
+
return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
|
|
358
|
+
}
|
|
359
|
+
function validateWireSubAccount(raw) {
|
|
360
|
+
if (typeof raw !== "object" || raw === null) {
|
|
361
|
+
return { ok: false, reason: "not an object" };
|
|
362
|
+
}
|
|
363
|
+
const r = raw;
|
|
364
|
+
if (r.object !== "sub_account") {
|
|
365
|
+
return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
|
|
366
|
+
}
|
|
367
|
+
if (typeof r.id !== "string" || r.id.length === 0) {
|
|
368
|
+
return { ok: false, reason: "id must be a non-empty string" };
|
|
369
|
+
}
|
|
370
|
+
if (!isParentShape(r.parent)) {
|
|
371
|
+
return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
|
|
372
|
+
}
|
|
373
|
+
if (typeof r.name !== "string") {
|
|
374
|
+
return { ok: false, reason: "name must be a string" };
|
|
375
|
+
}
|
|
376
|
+
if (r.purpose !== void 0 && typeof r.purpose !== "string") {
|
|
377
|
+
return { ok: false, reason: "purpose must be a string when present" };
|
|
378
|
+
}
|
|
379
|
+
if (r.status !== "active" && r.status !== "archived") {
|
|
380
|
+
return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
|
|
381
|
+
}
|
|
382
|
+
if (!isMoneyShape(r.balance)) {
|
|
383
|
+
return { ok: false, reason: "balance must be { value: string, currency: string }" };
|
|
384
|
+
}
|
|
385
|
+
if (!isFiniteNonNegativeInteger(r.createdAt)) {
|
|
386
|
+
return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
|
|
387
|
+
}
|
|
388
|
+
if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
|
|
389
|
+
return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
|
|
390
|
+
}
|
|
391
|
+
return { ok: true, value: r };
|
|
392
|
+
}
|
|
393
|
+
function brandSubAccount(raw) {
|
|
394
|
+
const result = validateWireSubAccount(raw);
|
|
395
|
+
if (!result.ok) {
|
|
396
|
+
throw malformedWireError(result.reason, raw);
|
|
397
|
+
}
|
|
398
|
+
const wire = result.value;
|
|
399
|
+
return {
|
|
400
|
+
object: wire.object,
|
|
401
|
+
id: toSubAccountId(wire.id),
|
|
402
|
+
parent: wire.parent,
|
|
403
|
+
name: wire.name,
|
|
404
|
+
...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
|
|
405
|
+
status: wire.status,
|
|
406
|
+
balance: wire.balance,
|
|
407
|
+
createdAt: new Date(wire.createdAt).toISOString()
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function tryBrandSubAccount(raw) {
|
|
411
|
+
try {
|
|
412
|
+
return [null, brandSubAccount(raw)];
|
|
413
|
+
} catch (err) {
|
|
414
|
+
if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
|
|
415
|
+
return [err, null];
|
|
416
|
+
}
|
|
417
|
+
return [
|
|
418
|
+
malformedWireError(
|
|
419
|
+
err instanceof Error ? err.message : String(err),
|
|
420
|
+
raw
|
|
421
|
+
),
|
|
422
|
+
null
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function createSubAccountsClient(config = {}) {
|
|
427
|
+
return {
|
|
428
|
+
retrieve: async (subAccountId) => {
|
|
429
|
+
if (!config.data) {
|
|
430
|
+
return stub("subAccounts.retrieve");
|
|
431
|
+
}
|
|
432
|
+
const [err, raw] = await tryCatch(
|
|
433
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
434
|
+
subAccountId
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
if (err) {
|
|
438
|
+
return [
|
|
439
|
+
fromConvexError(err),
|
|
440
|
+
null
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
if (!raw) {
|
|
444
|
+
return [
|
|
445
|
+
new CapxulError({
|
|
446
|
+
code: "NOT_FOUND",
|
|
447
|
+
message: `sub_account ${subAccountId} not found`
|
|
448
|
+
}),
|
|
449
|
+
null
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
453
|
+
if (brandErr) {
|
|
454
|
+
return [brandErr, null];
|
|
455
|
+
}
|
|
456
|
+
return [null, branded];
|
|
457
|
+
},
|
|
458
|
+
remove: async (subAccountId) => {
|
|
459
|
+
if (!config.data) {
|
|
460
|
+
return stub("subAccounts.remove");
|
|
461
|
+
}
|
|
462
|
+
const [err, raw] = await tryCatch(
|
|
463
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
464
|
+
subAccountId
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
if (err) {
|
|
468
|
+
return [
|
|
469
|
+
fromConvexError(err),
|
|
470
|
+
null
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
if (!raw) {
|
|
474
|
+
return [
|
|
475
|
+
new CapxulError({
|
|
476
|
+
code: "NOT_FOUND",
|
|
477
|
+
message: `sub_account ${subAccountId} not found`
|
|
478
|
+
}),
|
|
479
|
+
null
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
483
|
+
if (brandErr) {
|
|
484
|
+
return [brandErr, null];
|
|
485
|
+
}
|
|
486
|
+
return [null, branded];
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
73
491
|
// src/core/accounts.ts
|
|
492
|
+
function createAccountExternalAccountsClient(config) {
|
|
493
|
+
return {
|
|
494
|
+
create: async (input) => {
|
|
495
|
+
if (!config.data) {
|
|
496
|
+
return stub(
|
|
497
|
+
"accounts.externalAccounts.create"
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
const [err, raw] = await tryCatch(
|
|
501
|
+
config.data.mutation(
|
|
502
|
+
api.externalAccounts.mutations.createPersonal,
|
|
503
|
+
{
|
|
504
|
+
kind: input.kind,
|
|
505
|
+
label: input.label,
|
|
506
|
+
address: input.address,
|
|
507
|
+
iban: input.iban,
|
|
508
|
+
bic: input.bic,
|
|
509
|
+
accountHolder: input.accountHolder,
|
|
510
|
+
network: input.network,
|
|
511
|
+
panToken: input.panToken,
|
|
512
|
+
last4: input.last4
|
|
513
|
+
}
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
if (err) {
|
|
517
|
+
return [
|
|
518
|
+
fromConvexError(err),
|
|
519
|
+
null
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
if (!raw) {
|
|
523
|
+
return [
|
|
524
|
+
new CapxulError({
|
|
525
|
+
code: "NOT_FOUND",
|
|
526
|
+
message: "external_account creation returned no resource"
|
|
527
|
+
}),
|
|
528
|
+
null
|
|
529
|
+
];
|
|
530
|
+
}
|
|
531
|
+
return [
|
|
532
|
+
null,
|
|
533
|
+
brandExternalAccount(
|
|
534
|
+
raw
|
|
535
|
+
)
|
|
536
|
+
];
|
|
537
|
+
},
|
|
538
|
+
list: async (input) => {
|
|
539
|
+
if (!config.data) {
|
|
540
|
+
return stub(
|
|
541
|
+
"accounts.externalAccounts.list"
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const [err, result] = await tryCatch(
|
|
545
|
+
config.data.query(api.externalAccounts.queries.listPersonal, {
|
|
546
|
+
limit: input.limit,
|
|
547
|
+
cursor: input.cursor
|
|
548
|
+
})
|
|
549
|
+
);
|
|
550
|
+
if (err) {
|
|
551
|
+
return [fromConvexError(err), null];
|
|
552
|
+
}
|
|
553
|
+
const branded = result.data.map(
|
|
554
|
+
(row) => brandExternalAccount(
|
|
555
|
+
row
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
return [
|
|
559
|
+
null,
|
|
560
|
+
{
|
|
561
|
+
object: "list",
|
|
562
|
+
data: branded,
|
|
563
|
+
page: result.page
|
|
564
|
+
}
|
|
565
|
+
];
|
|
566
|
+
},
|
|
567
|
+
retrieve: async (externalAccountId) => {
|
|
568
|
+
if (!config.data) {
|
|
569
|
+
return stub(
|
|
570
|
+
"accounts.externalAccounts.retrieve"
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
const [err, raw] = await tryCatch(
|
|
574
|
+
config.data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
575
|
+
externalAccountId
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
if (err) {
|
|
579
|
+
return [
|
|
580
|
+
fromConvexError(err),
|
|
581
|
+
null
|
|
582
|
+
];
|
|
583
|
+
}
|
|
584
|
+
if (!raw) {
|
|
585
|
+
return [
|
|
586
|
+
new CapxulError({
|
|
587
|
+
code: "NOT_FOUND",
|
|
588
|
+
message: `external_account ${externalAccountId} not found`
|
|
589
|
+
}),
|
|
590
|
+
null
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
return [
|
|
594
|
+
null,
|
|
595
|
+
brandExternalAccount(
|
|
596
|
+
raw
|
|
597
|
+
)
|
|
598
|
+
];
|
|
599
|
+
},
|
|
600
|
+
remove: async (externalAccountId) => {
|
|
601
|
+
if (!config.data) {
|
|
602
|
+
return stub("accounts.externalAccounts.remove");
|
|
603
|
+
}
|
|
604
|
+
const [err] = await tryCatch(
|
|
605
|
+
config.data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
606
|
+
externalAccountId
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
if (err) {
|
|
610
|
+
return [
|
|
611
|
+
fromConvexError(err),
|
|
612
|
+
null
|
|
613
|
+
];
|
|
614
|
+
}
|
|
615
|
+
return [null, void 0];
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function createAccountSubAccountsClient(config) {
|
|
620
|
+
return {
|
|
621
|
+
create: async (input) => {
|
|
622
|
+
if (!config.data) {
|
|
623
|
+
return stub(
|
|
624
|
+
"accounts.subAccounts.create"
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const [err, raw] = await tryCatch(
|
|
628
|
+
config.data.mutation(api.subAccounts.mutations.create, {
|
|
629
|
+
parent: { kind: "account", id: input.accountId },
|
|
630
|
+
name: input.name,
|
|
631
|
+
purpose: input.purpose
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
if (err) {
|
|
635
|
+
return [
|
|
636
|
+
fromConvexError(err),
|
|
637
|
+
null
|
|
638
|
+
];
|
|
639
|
+
}
|
|
640
|
+
if (!raw) {
|
|
641
|
+
return [
|
|
642
|
+
new CapxulError({
|
|
643
|
+
code: "NOT_FOUND",
|
|
644
|
+
message: "sub_account creation returned no resource"
|
|
645
|
+
}),
|
|
646
|
+
null
|
|
647
|
+
];
|
|
648
|
+
}
|
|
649
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
650
|
+
if (brandErr) {
|
|
651
|
+
return [
|
|
652
|
+
brandErr,
|
|
653
|
+
null
|
|
654
|
+
];
|
|
655
|
+
}
|
|
656
|
+
return [null, branded];
|
|
657
|
+
},
|
|
658
|
+
list: async (input) => {
|
|
659
|
+
if (!config.data) {
|
|
660
|
+
return stub(
|
|
661
|
+
"accounts.subAccounts.list"
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const [err, rows] = await tryCatch(
|
|
665
|
+
config.data.query(api.subAccounts.queries.listByAccount, {
|
|
666
|
+
accountId: input.accountId
|
|
667
|
+
})
|
|
668
|
+
);
|
|
669
|
+
if (err) {
|
|
670
|
+
return [
|
|
671
|
+
fromConvexError(err),
|
|
672
|
+
null
|
|
673
|
+
];
|
|
674
|
+
}
|
|
675
|
+
const branded = [];
|
|
676
|
+
for (const row of rows) {
|
|
677
|
+
const [brandErr, value] = tryBrandSubAccount(row);
|
|
678
|
+
if (brandErr) {
|
|
679
|
+
return [
|
|
680
|
+
brandErr,
|
|
681
|
+
null
|
|
682
|
+
];
|
|
683
|
+
}
|
|
684
|
+
branded.push(value);
|
|
685
|
+
}
|
|
686
|
+
return [
|
|
687
|
+
null,
|
|
688
|
+
{
|
|
689
|
+
object: "list",
|
|
690
|
+
data: branded,
|
|
691
|
+
page: { hasMore: false }
|
|
692
|
+
}
|
|
693
|
+
];
|
|
694
|
+
},
|
|
695
|
+
retrieve: async (subAccountId) => {
|
|
696
|
+
if (!config.data) {
|
|
697
|
+
return stub(
|
|
698
|
+
"accounts.subAccounts.retrieve"
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const [err, raw] = await tryCatch(
|
|
702
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
703
|
+
subAccountId
|
|
704
|
+
})
|
|
705
|
+
);
|
|
706
|
+
if (err) {
|
|
707
|
+
return [
|
|
708
|
+
fromConvexError(err),
|
|
709
|
+
null
|
|
710
|
+
];
|
|
711
|
+
}
|
|
712
|
+
if (!raw) {
|
|
713
|
+
return [
|
|
714
|
+
new CapxulError({
|
|
715
|
+
code: "NOT_FOUND",
|
|
716
|
+
message: `sub_account ${subAccountId} not found`
|
|
717
|
+
}),
|
|
718
|
+
null
|
|
719
|
+
];
|
|
720
|
+
}
|
|
721
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
722
|
+
if (brandErr) {
|
|
723
|
+
return [
|
|
724
|
+
brandErr,
|
|
725
|
+
null
|
|
726
|
+
];
|
|
727
|
+
}
|
|
728
|
+
return [null, branded];
|
|
729
|
+
},
|
|
730
|
+
remove: async (subAccountId) => {
|
|
731
|
+
if (!config.data) {
|
|
732
|
+
return stub(
|
|
733
|
+
"accounts.subAccounts.remove"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const [err, raw] = await tryCatch(
|
|
737
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
738
|
+
subAccountId
|
|
739
|
+
})
|
|
740
|
+
);
|
|
741
|
+
if (err) {
|
|
742
|
+
return [
|
|
743
|
+
fromConvexError(err),
|
|
744
|
+
null
|
|
745
|
+
];
|
|
746
|
+
}
|
|
747
|
+
if (!raw) {
|
|
748
|
+
return [
|
|
749
|
+
new CapxulError({
|
|
750
|
+
code: "NOT_FOUND",
|
|
751
|
+
message: `sub_account ${subAccountId} not found`
|
|
752
|
+
}),
|
|
753
|
+
null
|
|
754
|
+
];
|
|
755
|
+
}
|
|
756
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
757
|
+
if (brandErr) {
|
|
758
|
+
return [
|
|
759
|
+
brandErr,
|
|
760
|
+
null
|
|
761
|
+
];
|
|
762
|
+
}
|
|
763
|
+
return [null, branded];
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
}
|
|
74
767
|
function createAccountsClient(config = {}) {
|
|
75
768
|
return {
|
|
76
|
-
retrieve: async () =>
|
|
769
|
+
retrieve: async (accountId) => {
|
|
770
|
+
if (!config.data) {
|
|
771
|
+
return stub("accounts.retrieve");
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
const account = await config.data.query(
|
|
775
|
+
api.openfort.queries.getMyAccount,
|
|
776
|
+
{}
|
|
777
|
+
);
|
|
778
|
+
if (account.id !== accountId) {
|
|
779
|
+
return [
|
|
780
|
+
new CapxulError({
|
|
781
|
+
code: "PERMISSION_DENIED",
|
|
782
|
+
message: "accounts.retrieve currently supports the authenticated caller's own account only.",
|
|
783
|
+
details: {
|
|
784
|
+
requestedAccountId: accountId,
|
|
785
|
+
authenticatedAccountId: account.id
|
|
786
|
+
}
|
|
787
|
+
}),
|
|
788
|
+
null
|
|
789
|
+
];
|
|
790
|
+
}
|
|
791
|
+
return [null, account];
|
|
792
|
+
} catch (cause) {
|
|
793
|
+
return [fromConvexError(cause), null];
|
|
794
|
+
}
|
|
795
|
+
},
|
|
77
796
|
lookup: async () => stub("accounts.lookup"),
|
|
78
|
-
update: async () =>
|
|
797
|
+
update: async (input) => {
|
|
798
|
+
if (!config.data) {
|
|
799
|
+
return stub("accounts.update");
|
|
800
|
+
}
|
|
801
|
+
if (input.countryCode !== void 0) {
|
|
802
|
+
return [
|
|
803
|
+
new CapxulError({
|
|
804
|
+
code: "INVALID_INPUT",
|
|
805
|
+
message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
806
|
+
details: { field: "countryCode" }
|
|
807
|
+
}),
|
|
808
|
+
null
|
|
809
|
+
];
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
const current = await config.data.query(
|
|
813
|
+
api.openfort.queries.getMyAccount,
|
|
814
|
+
{}
|
|
815
|
+
);
|
|
816
|
+
if (current.id !== input.accountId) {
|
|
817
|
+
return [
|
|
818
|
+
new CapxulError({
|
|
819
|
+
code: "PERMISSION_DENIED",
|
|
820
|
+
message: "accounts.update currently supports the authenticated caller's own account only.",
|
|
821
|
+
details: {
|
|
822
|
+
requestedAccountId: input.accountId,
|
|
823
|
+
authenticatedAccountId: current.id
|
|
824
|
+
}
|
|
825
|
+
}),
|
|
826
|
+
null
|
|
827
|
+
];
|
|
828
|
+
}
|
|
829
|
+
await config.data.mutation(api.openfort.mutations.updateProfile, {
|
|
830
|
+
displayName: input.name,
|
|
831
|
+
username: input.username
|
|
832
|
+
});
|
|
833
|
+
const updated = await config.data.query(
|
|
834
|
+
api.openfort.queries.getMyAccount,
|
|
835
|
+
{}
|
|
836
|
+
);
|
|
837
|
+
return [null, updated];
|
|
838
|
+
} catch (cause) {
|
|
839
|
+
return [fromConvexError(cause), null];
|
|
840
|
+
}
|
|
841
|
+
},
|
|
79
842
|
provisionPersonal: async (input) => {
|
|
80
843
|
if (!config.data) {
|
|
81
844
|
return stub(
|
|
@@ -155,24 +918,8 @@ function createAccountsClient(config = {}) {
|
|
|
155
918
|
create: async () => stub("accounts.kycProfiles.create"),
|
|
156
919
|
retrieve: async () => stub("accounts.kycProfiles.retrieve")
|
|
157
920
|
},
|
|
158
|
-
externalAccounts:
|
|
159
|
-
|
|
160
|
-
"accounts.externalAccounts.create"
|
|
161
|
-
),
|
|
162
|
-
list: async () => stub(
|
|
163
|
-
"accounts.externalAccounts.list"
|
|
164
|
-
),
|
|
165
|
-
retrieve: async () => stub(
|
|
166
|
-
"accounts.externalAccounts.retrieve"
|
|
167
|
-
),
|
|
168
|
-
remove: async () => stub("accounts.externalAccounts.remove")
|
|
169
|
-
},
|
|
170
|
-
subAccounts: {
|
|
171
|
-
create: async () => stub("accounts.subAccounts.create"),
|
|
172
|
-
list: async () => stub("accounts.subAccounts.list"),
|
|
173
|
-
retrieve: async () => stub("accounts.subAccounts.retrieve"),
|
|
174
|
-
remove: async () => stub("accounts.subAccounts.remove")
|
|
175
|
-
},
|
|
921
|
+
externalAccounts: createAccountExternalAccountsClient(config),
|
|
922
|
+
subAccounts: createAccountSubAccountsClient(config),
|
|
176
923
|
balanceLedger: {
|
|
177
924
|
list: async () => stub(
|
|
178
925
|
"accounts.balanceLedger.list"
|
|
@@ -232,10 +979,16 @@ var Errors = {
|
|
|
232
979
|
`Shield API error (${status}): ${detail}`,
|
|
233
980
|
{ details: { provider: "shield", status } }
|
|
234
981
|
),
|
|
235
|
-
providerError: (provider, operation, cause) =>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
982
|
+
providerError: (provider, operation, cause) => (
|
|
983
|
+
// Public `message` is redacted to a fixed shape so provider-side
|
|
984
|
+
// exception text never leaks to the client. The original `cause`
|
|
985
|
+
// is preserved on `Error.cause` for server-side debugging via
|
|
986
|
+
// observability sinks (Sentry, console traces).
|
|
987
|
+
new CapxulError2(
|
|
988
|
+
"PROVIDER_ERROR",
|
|
989
|
+
`Provider error: ${provider} ${operation}`,
|
|
990
|
+
{ cause, details: { provider, operation } }
|
|
991
|
+
)
|
|
239
992
|
),
|
|
240
993
|
invalidInput: (field, reason) => new CapxulError2(
|
|
241
994
|
"INVALID_INPUT",
|
|
@@ -261,8 +1014,35 @@ var Errors = {
|
|
|
261
1014
|
"Idempotency key was already used for a different request",
|
|
262
1015
|
{ details }
|
|
263
1016
|
),
|
|
264
|
-
emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}
|
|
265
|
-
|
|
1017
|
+
emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
|
|
1018
|
+
details
|
|
1019
|
+
}),
|
|
1020
|
+
rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
|
|
1021
|
+
details: { ...details }
|
|
1022
|
+
}),
|
|
1023
|
+
internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
1024
|
+
/**
|
|
1025
|
+
* Verification gate. Surfaced when a request hits a verification
|
|
1026
|
+
* boundary the actor cannot cross under their current state. Two
|
|
1027
|
+
* variants share this code:
|
|
1028
|
+
*
|
|
1029
|
+
* - Rail gate (Withdrawals v1 W2, #465): the resolved
|
|
1030
|
+
* `external_account.kind` routes to a withdrawal rail (e.g.
|
|
1031
|
+
* `fiat_offramp`, `card_payout`) that is not yet supported. Carries
|
|
1032
|
+
* `details.rail` + `details.currentKind`.
|
|
1033
|
+
* - KYC tier gate (legacy / future): the actor's KYC tier is below
|
|
1034
|
+
* the required tier. Carries `details.requiredTier`.
|
|
1035
|
+
*
|
|
1036
|
+
* Code is shared because both expose the same UX shape ("you cannot
|
|
1037
|
+
* proceed until verification advances"); the `details.*` keys
|
|
1038
|
+
* differentiate the route.
|
|
1039
|
+
*/
|
|
1040
|
+
verificationRequired: (details) => {
|
|
1041
|
+
const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
|
|
1042
|
+
return new CapxulError2("VERIFICATION_REQUIRED", message, {
|
|
1043
|
+
details: { ...details }
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
266
1046
|
};
|
|
267
1047
|
|
|
268
1048
|
// ../config/src/safe.ts
|
|
@@ -312,13 +1092,13 @@ function createLifecycle(initial) {
|
|
|
312
1092
|
}
|
|
313
1093
|
function makeBuildTimeUrlsTransport(config) {
|
|
314
1094
|
if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
|
|
315
|
-
throw
|
|
1095
|
+
throw invalidConfigError(
|
|
316
1096
|
"authBaseUrl",
|
|
317
1097
|
"build-time-urls transport requires a non-empty authBaseUrl."
|
|
318
1098
|
);
|
|
319
1099
|
}
|
|
320
1100
|
if (!config.convexUrl || config.convexUrl.trim().length === 0) {
|
|
321
|
-
throw
|
|
1101
|
+
throw invalidConfigError(
|
|
322
1102
|
"convexUrl",
|
|
323
1103
|
"build-time-urls transport requires a non-empty convexUrl."
|
|
324
1104
|
);
|
|
@@ -332,6 +1112,7 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
332
1112
|
return {
|
|
333
1113
|
authBaseUrl,
|
|
334
1114
|
convexUrl,
|
|
1115
|
+
ensureRuntime: async () => runtime,
|
|
335
1116
|
fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
|
|
336
1117
|
getState: lifecycle.getState,
|
|
337
1118
|
subscribe: lifecycle.subscribe,
|
|
@@ -347,14 +1128,15 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
347
1128
|
};
|
|
348
1129
|
}
|
|
349
1130
|
function makePublishableKeyTransport(config) {
|
|
350
|
-
|
|
351
|
-
|
|
1131
|
+
const publishableKey = config.publishableKey?.trim();
|
|
1132
|
+
if (!publishableKey) {
|
|
1133
|
+
throw invalidConfigError(
|
|
352
1134
|
"publishableKey",
|
|
353
1135
|
"publishable-key transport requires a non-empty publishableKey."
|
|
354
1136
|
);
|
|
355
1137
|
}
|
|
356
1138
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
357
|
-
const bootstrapUrl =
|
|
1139
|
+
const bootstrapUrl = normalizeBootstrapUrl(
|
|
358
1140
|
config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
|
|
359
1141
|
);
|
|
360
1142
|
let authBaseUrl = "";
|
|
@@ -369,23 +1151,20 @@ function makePublishableKeyTransport(config) {
|
|
|
369
1151
|
const response = await fetchImpl(bootstrapUrl, {
|
|
370
1152
|
method: "POST",
|
|
371
1153
|
headers: { "content-type": "application/json" },
|
|
372
|
-
body: JSON.stringify({ publishableKey
|
|
1154
|
+
body: JSON.stringify({ publishableKey })
|
|
373
1155
|
});
|
|
374
1156
|
if (!response.ok) {
|
|
375
|
-
throw
|
|
376
|
-
"publishableKey",
|
|
377
|
-
`${bootstrapUrl} failed with HTTP ${response.status}.`
|
|
378
|
-
);
|
|
1157
|
+
throw await bootstrapResponseError(response, bootstrapUrl);
|
|
379
1158
|
}
|
|
380
|
-
const body = await response
|
|
1159
|
+
const body = await readBootstrapSuccessBody(response);
|
|
381
1160
|
if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
|
|
382
|
-
throw
|
|
1161
|
+
throw bootstrapContractError(
|
|
383
1162
|
"authBaseUrl",
|
|
384
1163
|
"/v1/client/bootstrap returned no authBaseUrl."
|
|
385
1164
|
);
|
|
386
1165
|
}
|
|
387
1166
|
if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
|
|
388
|
-
throw
|
|
1167
|
+
throw bootstrapContractError(
|
|
389
1168
|
"convexUrl",
|
|
390
1169
|
"/v1/client/bootstrap returned no convexUrl."
|
|
391
1170
|
);
|
|
@@ -397,16 +1176,13 @@ function makePublishableKeyTransport(config) {
|
|
|
397
1176
|
return runtime;
|
|
398
1177
|
})();
|
|
399
1178
|
bootstrapPromise = attempt.catch((err) => {
|
|
1179
|
+
const error = normalizeBootstrapThrownError(err);
|
|
400
1180
|
bootstrapPromise = null;
|
|
401
1181
|
lifecycle.setState({
|
|
402
1182
|
status: "error",
|
|
403
|
-
error
|
|
404
|
-
code: "UNKNOWN",
|
|
405
|
-
message: "Bootstrap failed without a typed CapxulError.",
|
|
406
|
-
cause: err
|
|
407
|
-
})
|
|
1183
|
+
error
|
|
408
1184
|
});
|
|
409
|
-
throw
|
|
1185
|
+
throw error;
|
|
410
1186
|
});
|
|
411
1187
|
return await bootstrapPromise;
|
|
412
1188
|
}
|
|
@@ -417,6 +1193,7 @@ function makePublishableKeyTransport(config) {
|
|
|
417
1193
|
get convexUrl() {
|
|
418
1194
|
return convexUrl;
|
|
419
1195
|
},
|
|
1196
|
+
ensureRuntime: ensureBootstrap,
|
|
420
1197
|
fetch: async (path, init) => {
|
|
421
1198
|
const resolved = await ensureBootstrap();
|
|
422
1199
|
return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
|
|
@@ -427,7 +1204,7 @@ function makePublishableKeyTransport(config) {
|
|
|
427
1204
|
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
428
1205
|
const current = lifecycle.getState();
|
|
429
1206
|
if (current.status !== "ready" && current.status !== "authenticated") {
|
|
430
|
-
throw
|
|
1207
|
+
throw internalTransportError(
|
|
431
1208
|
`markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
|
|
432
1209
|
);
|
|
433
1210
|
}
|
|
@@ -449,6 +1226,24 @@ function makePublishableKeyTransport(config) {
|
|
|
449
1226
|
function stripTrailingSlash(url) {
|
|
450
1227
|
return url.replace(/\/+$/, "");
|
|
451
1228
|
}
|
|
1229
|
+
function normalizeBootstrapUrl(url) {
|
|
1230
|
+
const normalized = stripTrailingSlash(url.trim());
|
|
1231
|
+
if (!isAbsoluteHttpUrl(normalized)) {
|
|
1232
|
+
throw invalidConfigError(
|
|
1233
|
+
"bootstrapUrl",
|
|
1234
|
+
"publishable-key transport requires an absolute http(s) bootstrapUrl."
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
return normalized;
|
|
1238
|
+
}
|
|
1239
|
+
function isAbsoluteHttpUrl(url) {
|
|
1240
|
+
try {
|
|
1241
|
+
const parsed = new URL(url);
|
|
1242
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
1243
|
+
} catch {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
452
1247
|
function resolveUrl(authBaseUrl, path) {
|
|
453
1248
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
454
1249
|
return path;
|
|
@@ -456,10 +1251,142 @@ function resolveUrl(authBaseUrl, path) {
|
|
|
456
1251
|
return `${authBaseUrl}${path}`;
|
|
457
1252
|
}
|
|
458
1253
|
function assertNever(value) {
|
|
459
|
-
throw
|
|
1254
|
+
throw internalTransportError(
|
|
460
1255
|
`Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
|
|
461
1256
|
);
|
|
462
1257
|
}
|
|
1258
|
+
function invalidConfigError(field, reason) {
|
|
1259
|
+
return new CapxulError({
|
|
1260
|
+
code: "INVALID_INPUT",
|
|
1261
|
+
message: `Invalid ${field}: ${reason}`,
|
|
1262
|
+
details: { source: "sdk-config", field, reason }
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
function bootstrapContractError(field, message) {
|
|
1266
|
+
return new CapxulError({
|
|
1267
|
+
code: "INVALID_INPUT",
|
|
1268
|
+
message,
|
|
1269
|
+
details: {
|
|
1270
|
+
source: "backend-bootstrap",
|
|
1271
|
+
phase: "publishable-key-bootstrap",
|
|
1272
|
+
field,
|
|
1273
|
+
reason: message
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
function internalTransportError(reason) {
|
|
1278
|
+
return new CapxulError({
|
|
1279
|
+
code: "INTERNAL_ERROR",
|
|
1280
|
+
message: `Internal error: ${reason}`,
|
|
1281
|
+
details: { source: "sdk-transport", reason }
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
async function bootstrapResponseError(response, bootstrapUrl) {
|
|
1285
|
+
const envelope = await readBootstrapErrorEnvelope(response);
|
|
1286
|
+
const wireCode = readNonEmptyString(envelope?.error?.code);
|
|
1287
|
+
const normalized = normalizeBootstrapErrorCode(wireCode);
|
|
1288
|
+
const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
|
|
1289
|
+
const backendDetails = readRecord(envelope?.error?.details);
|
|
1290
|
+
return new CapxulError({
|
|
1291
|
+
code: normalized.code,
|
|
1292
|
+
message,
|
|
1293
|
+
details: {
|
|
1294
|
+
...backendDetails,
|
|
1295
|
+
source: "backend-bootstrap",
|
|
1296
|
+
phase: "publishable-key-bootstrap",
|
|
1297
|
+
httpStatus: response.status,
|
|
1298
|
+
...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
|
|
1299
|
+
},
|
|
1300
|
+
operationId: readNonEmptyString(envelope?.error?.operationId),
|
|
1301
|
+
correlationId: readNonEmptyString(envelope?.error?.correlationId),
|
|
1302
|
+
retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
async function readBootstrapErrorEnvelope(response) {
|
|
1306
|
+
try {
|
|
1307
|
+
const parsed = await response.json();
|
|
1308
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1309
|
+
} catch {
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
async function readBootstrapSuccessBody(response) {
|
|
1314
|
+
try {
|
|
1315
|
+
const parsed = await response.json();
|
|
1316
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1317
|
+
} catch {
|
|
1318
|
+
throw bootstrapContractError(
|
|
1319
|
+
"body",
|
|
1320
|
+
"/v1/client/bootstrap returned invalid JSON."
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function normalizeBootstrapThrownError(error) {
|
|
1325
|
+
if (error instanceof CapxulError) return error;
|
|
1326
|
+
return new CapxulError({
|
|
1327
|
+
code: "NETWORK_ERROR",
|
|
1328
|
+
message: "Publishable-key bootstrap network failure.",
|
|
1329
|
+
cause: error,
|
|
1330
|
+
details: {
|
|
1331
|
+
source: "bootstrap-network",
|
|
1332
|
+
phase: "publishable-key-bootstrap"
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
function normalizeBootstrapErrorCode(wireCode) {
|
|
1337
|
+
if (wireCode === "INTERNAL_SERVER_ERROR") {
|
|
1338
|
+
return { code: "INTERNAL_ERROR", wireCode };
|
|
1339
|
+
}
|
|
1340
|
+
if (wireCode && isCapxulErrorCode(wireCode)) {
|
|
1341
|
+
return { code: wireCode };
|
|
1342
|
+
}
|
|
1343
|
+
return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
|
|
1344
|
+
}
|
|
1345
|
+
var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1346
|
+
"NOT_AUTHENTICATED",
|
|
1347
|
+
"EMAIL_DELIVERY_FAILED",
|
|
1348
|
+
"PROFILE_NOT_FOUND",
|
|
1349
|
+
"SMART_ACCOUNT_MISSING",
|
|
1350
|
+
"PLAYER_NOT_FOUND",
|
|
1351
|
+
"ACCOUNT_NOT_FOUND",
|
|
1352
|
+
"PROVIDER_ERROR",
|
|
1353
|
+
"INVALID_INPUT",
|
|
1354
|
+
"ENV_MISSING",
|
|
1355
|
+
"NOT_IMPLEMENTED",
|
|
1356
|
+
"VERIFICATION_REQUIRED",
|
|
1357
|
+
"INSUFFICIENT_BALANCE",
|
|
1358
|
+
"INVALID_RECIPIENT",
|
|
1359
|
+
"TRANSACTION_FAILED",
|
|
1360
|
+
"RATE_LIMITED",
|
|
1361
|
+
"NETWORK_ERROR",
|
|
1362
|
+
"UNKNOWN",
|
|
1363
|
+
"PERMISSION_DENIED",
|
|
1364
|
+
"API_KEY_INVALID",
|
|
1365
|
+
"API_KEY_EXPIRED",
|
|
1366
|
+
"IDEMPOTENCY_CONFLICT",
|
|
1367
|
+
"NOT_FOUND",
|
|
1368
|
+
"OPERATION_CANCELED",
|
|
1369
|
+
"OPERATION_TIMEOUT",
|
|
1370
|
+
"ACTION_REQUIRED",
|
|
1371
|
+
"KYC_REQUIRED",
|
|
1372
|
+
"POLICY_DENIED",
|
|
1373
|
+
"SAFE_NOT_READY",
|
|
1374
|
+
"PROVIDER_UNAVAILABLE",
|
|
1375
|
+
"PROVIDER_REJECTED",
|
|
1376
|
+
"RECONCILIATION_FAILED",
|
|
1377
|
+
"INTERNAL_ERROR",
|
|
1378
|
+
"QUOTE_EXPIRED",
|
|
1379
|
+
"QUOTE_NOT_FOUND"
|
|
1380
|
+
]);
|
|
1381
|
+
function isCapxulErrorCode(value) {
|
|
1382
|
+
return CAPXUL_ERROR_CODES.has(value);
|
|
1383
|
+
}
|
|
1384
|
+
function readRecord(value) {
|
|
1385
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
1386
|
+
}
|
|
1387
|
+
function readNonEmptyString(value) {
|
|
1388
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1389
|
+
}
|
|
463
1390
|
|
|
464
1391
|
// src/core/auth.ts
|
|
465
1392
|
function createAuthClient(config = {}) {
|
|
@@ -514,13 +1441,16 @@ function createAuthClient(config = {}) {
|
|
|
514
1441
|
email: signIn.user.email,
|
|
515
1442
|
token: signIn.token,
|
|
516
1443
|
convexJwt,
|
|
517
|
-
expiresAt: new Date(
|
|
1444
|
+
expiresAt: new Date(
|
|
1445
|
+
Date.now() + 30 * 24 * 60 * 60 * 1e3
|
|
1446
|
+
).toISOString()
|
|
518
1447
|
};
|
|
519
1448
|
sessionStore.set(session);
|
|
520
1449
|
if (config.auth?.createDataClient) {
|
|
521
1450
|
try {
|
|
522
1451
|
dataClient = await config.auth.createDataClient(session);
|
|
523
1452
|
mutableConfig(config).data = dataClient;
|
|
1453
|
+
transport.markAuthenticated({ dataClient });
|
|
524
1454
|
} catch (cause) {
|
|
525
1455
|
return [
|
|
526
1456
|
new CapxulError({
|
|
@@ -532,13 +1462,76 @@ function createAuthClient(config = {}) {
|
|
|
532
1462
|
];
|
|
533
1463
|
}
|
|
534
1464
|
}
|
|
535
|
-
|
|
1465
|
+
if (!dataClient) {
|
|
1466
|
+
return [
|
|
1467
|
+
new CapxulError({
|
|
1468
|
+
code: "NOT_AUTHENTICATED",
|
|
1469
|
+
message: "Auth bootstrap requires an authenticated Convex data client."
|
|
1470
|
+
}),
|
|
1471
|
+
null
|
|
1472
|
+
];
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
const resolution = await dataClient.mutation(
|
|
1476
|
+
api.authBootstrap.resolveAfterOtp,
|
|
1477
|
+
{
|
|
1478
|
+
email: session.email,
|
|
1479
|
+
sessionToken: session.token
|
|
1480
|
+
}
|
|
1481
|
+
);
|
|
1482
|
+
if (resolution.kind === "existing_member") {
|
|
1483
|
+
return [null, { ...resolution, session }];
|
|
1484
|
+
}
|
|
1485
|
+
return [null, { ...resolution, session }];
|
|
1486
|
+
} catch (cause) {
|
|
1487
|
+
return [fromConvexError(cause), null];
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1490
|
+
completeBootstrap: async (input) => {
|
|
1491
|
+
const session = sessionStore.get();
|
|
1492
|
+
const data = dataClient ?? config.data;
|
|
1493
|
+
if (!session || !data) {
|
|
1494
|
+
return [
|
|
1495
|
+
new CapxulError({
|
|
1496
|
+
code: "INVALID_INPUT",
|
|
1497
|
+
message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
|
|
1498
|
+
}),
|
|
1499
|
+
null
|
|
1500
|
+
];
|
|
1501
|
+
}
|
|
1502
|
+
if (input.signerProvider.kind !== "local-private-key") {
|
|
1503
|
+
return [
|
|
1504
|
+
new CapxulError({
|
|
1505
|
+
code: "INVALID_INPUT",
|
|
1506
|
+
message: "completeBootstrap currently supports local-private-key signer providers only."
|
|
1507
|
+
}),
|
|
1508
|
+
null
|
|
1509
|
+
];
|
|
1510
|
+
}
|
|
1511
|
+
try {
|
|
1512
|
+
const result = await data.mutation(api.authBootstrap.completeBootstrap, {
|
|
1513
|
+
bootstrapToken: input.bootstrapToken,
|
|
1514
|
+
sessionToken: session.token,
|
|
1515
|
+
username: input.username,
|
|
1516
|
+
displayName: input.displayName,
|
|
1517
|
+
countryCode: input.countryCode,
|
|
1518
|
+
signerProvider: input.signerProvider
|
|
1519
|
+
});
|
|
1520
|
+
return [null, { kind: "authenticated", session, ...result }];
|
|
1521
|
+
} catch (cause) {
|
|
1522
|
+
return [
|
|
1523
|
+
fromConvexError(cause),
|
|
1524
|
+
null
|
|
1525
|
+
];
|
|
1526
|
+
}
|
|
536
1527
|
},
|
|
537
1528
|
getSession: async () => [null, sessionStore.get()],
|
|
538
1529
|
signOut: async () => {
|
|
539
1530
|
sessionStore.clear();
|
|
540
1531
|
dataClient = null;
|
|
541
1532
|
mutableConfig(config).data = void 0;
|
|
1533
|
+
const transport = getTransport();
|
|
1534
|
+
transport?.clearAuth();
|
|
542
1535
|
return [null, void 0];
|
|
543
1536
|
},
|
|
544
1537
|
serviceTokenMint: async () => stub("auth.serviceTokenMint"),
|
|
@@ -592,16 +1585,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
592
1585
|
body: JSON.stringify(body),
|
|
593
1586
|
signal
|
|
594
1587
|
});
|
|
1588
|
+
const text = await response.text();
|
|
595
1589
|
if (!response.ok) {
|
|
1590
|
+
const parsedError = parseBetterAuthError(text);
|
|
596
1591
|
return [
|
|
597
1592
|
new CapxulError({
|
|
598
|
-
code,
|
|
599
|
-
message: `BetterAuth ${path} failed with HTTP ${response.status}
|
|
1593
|
+
code: parsedError.code ?? code,
|
|
1594
|
+
message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
|
|
1595
|
+
details: parsedError.details,
|
|
1596
|
+
retryable: parsedError.retryable
|
|
600
1597
|
}),
|
|
601
1598
|
null
|
|
602
1599
|
];
|
|
603
1600
|
}
|
|
604
|
-
const text = await response.text();
|
|
605
1601
|
return [null, text ? JSON.parse(text) : void 0];
|
|
606
1602
|
} catch (cause) {
|
|
607
1603
|
return [
|
|
@@ -614,6 +1610,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
614
1610
|
];
|
|
615
1611
|
}
|
|
616
1612
|
}
|
|
1613
|
+
function parseBetterAuthError(text) {
|
|
1614
|
+
if (!text.trim()) {
|
|
1615
|
+
return {};
|
|
1616
|
+
}
|
|
1617
|
+
try {
|
|
1618
|
+
const body = JSON.parse(text);
|
|
1619
|
+
if (!body || typeof body !== "object") {
|
|
1620
|
+
return {};
|
|
1621
|
+
}
|
|
1622
|
+
const record = body;
|
|
1623
|
+
const nested = record.error && typeof record.error === "object" ? record.error : record;
|
|
1624
|
+
const code = typeof nested.code === "string" ? nested.code : void 0;
|
|
1625
|
+
const message = typeof nested.message === "string" ? nested.message : void 0;
|
|
1626
|
+
const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
|
|
1627
|
+
const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
|
|
1628
|
+
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
1629
|
+
return {
|
|
1630
|
+
code: isCapxulErrorCode2(code) ? code : void 0,
|
|
1631
|
+
message,
|
|
1632
|
+
details,
|
|
1633
|
+
correlationId,
|
|
1634
|
+
retryable
|
|
1635
|
+
};
|
|
1636
|
+
} catch {
|
|
1637
|
+
return {};
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
function isCapxulErrorCode2(code) {
|
|
1641
|
+
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";
|
|
1642
|
+
}
|
|
617
1643
|
async function exchangeConvexToken(transport, config, token, signal) {
|
|
618
1644
|
const path = config.auth?.convexTokenUrl ?? "/convex/token";
|
|
619
1645
|
try {
|
|
@@ -674,14 +1700,6 @@ function createOrgDocumentsClient() {
|
|
|
674
1700
|
};
|
|
675
1701
|
}
|
|
676
1702
|
|
|
677
|
-
// src/core/external-accounts.ts
|
|
678
|
-
function createExternalAccountsClient() {
|
|
679
|
-
return {
|
|
680
|
-
retrieve: async () => stub("externalAccounts.retrieve"),
|
|
681
|
-
remove: async () => stub("externalAccounts.remove")
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
|
|
685
1703
|
// src/core/me.ts
|
|
686
1704
|
function createMeClient(config = {}) {
|
|
687
1705
|
return {
|
|
@@ -699,7 +1717,34 @@ function createMeClient(config = {}) {
|
|
|
699
1717
|
return [fromConvexError(cause), null];
|
|
700
1718
|
}
|
|
701
1719
|
},
|
|
702
|
-
update: async () =>
|
|
1720
|
+
update: async (input) => {
|
|
1721
|
+
if (!config.data) {
|
|
1722
|
+
return stub("me.update");
|
|
1723
|
+
}
|
|
1724
|
+
if (input.countryCode !== void 0) {
|
|
1725
|
+
return [
|
|
1726
|
+
new CapxulError({
|
|
1727
|
+
code: "INVALID_INPUT",
|
|
1728
|
+
message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
1729
|
+
details: { field: "countryCode" }
|
|
1730
|
+
}),
|
|
1731
|
+
null
|
|
1732
|
+
];
|
|
1733
|
+
}
|
|
1734
|
+
try {
|
|
1735
|
+
await config.data.mutation(api.openfort.mutations.updateProfile, {
|
|
1736
|
+
displayName: input.name,
|
|
1737
|
+
username: input.username
|
|
1738
|
+
});
|
|
1739
|
+
const account = await config.data.query(
|
|
1740
|
+
api.openfort.queries.getMyAccount,
|
|
1741
|
+
{}
|
|
1742
|
+
);
|
|
1743
|
+
return [null, account];
|
|
1744
|
+
} catch (cause) {
|
|
1745
|
+
return [fromConvexError(cause), null];
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
703
1748
|
};
|
|
704
1749
|
}
|
|
705
1750
|
|
|
@@ -1003,70 +2048,464 @@ function createPaymentsClient(config = {}) {
|
|
|
1003
2048
|
if (submitted && created?.id) {
|
|
1004
2049
|
return [new CapxulError({
|
|
1005
2050
|
code: "NETWORK_ERROR",
|
|
1006
|
-
message: "Payment was submitted on-chain, but backend submission tracking failed.",
|
|
1007
|
-
cause,
|
|
1008
|
-
details: {
|
|
1009
|
-
paymentId: created.id,
|
|
1010
|
-
txHash: submitted.txHash,
|
|
1011
|
-
userOpHash: submitted.userOpHash
|
|
1012
|
-
}
|
|
1013
|
-
}), null];
|
|
1014
|
-
}
|
|
1015
|
-
return [error, null];
|
|
2051
|
+
message: "Payment was submitted on-chain, but backend submission tracking failed.",
|
|
2052
|
+
cause,
|
|
2053
|
+
details: {
|
|
2054
|
+
paymentId: created.id,
|
|
2055
|
+
txHash: submitted.txHash,
|
|
2056
|
+
userOpHash: submitted.userOpHash
|
|
2057
|
+
}
|
|
2058
|
+
}), null];
|
|
2059
|
+
}
|
|
2060
|
+
return [error, null];
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
retrieve: async (paymentId) => {
|
|
2064
|
+
if (!config.data) {
|
|
2065
|
+
return stub("payments.retrieve");
|
|
2066
|
+
}
|
|
2067
|
+
try {
|
|
2068
|
+
const payment = await config.data.query(api.payments.queries.retrieve, {
|
|
2069
|
+
paymentId
|
|
2070
|
+
});
|
|
2071
|
+
if (!payment) {
|
|
2072
|
+
return [new CapxulError({
|
|
2073
|
+
code: "NOT_FOUND",
|
|
2074
|
+
message: `payment ${paymentId} not found`
|
|
2075
|
+
}), null];
|
|
2076
|
+
}
|
|
2077
|
+
return [null, payment];
|
|
2078
|
+
} catch (cause) {
|
|
2079
|
+
return [fromConvexError(cause), null];
|
|
2080
|
+
}
|
|
2081
|
+
},
|
|
2082
|
+
list: async () => stub("payments.list")
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
function createOrgPaymentsClient() {
|
|
2086
|
+
return {
|
|
2087
|
+
create: async () => stub(
|
|
2088
|
+
"organizations.payments.create"
|
|
2089
|
+
),
|
|
2090
|
+
retrieve: async () => stub("organizations.payments.retrieve"),
|
|
2091
|
+
list: async () => stub("organizations.payments.list")
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
async function bestEffortMarkFailed(config, paymentId, error) {
|
|
2095
|
+
try {
|
|
2096
|
+
await config.data.mutation(api.payments.mutations.markFailed, {
|
|
2097
|
+
paymentId,
|
|
2098
|
+
errorCode: error.code,
|
|
2099
|
+
errorMessage: error.message,
|
|
2100
|
+
source: "sdk"
|
|
2101
|
+
});
|
|
2102
|
+
} catch {
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
function mapCreateError(error) {
|
|
2106
|
+
switch (error.code) {
|
|
2107
|
+
case "NOT_AUTHENTICATED":
|
|
2108
|
+
case "PERMISSION_DENIED":
|
|
2109
|
+
case "INVALID_INPUT":
|
|
2110
|
+
case "INVALID_RECIPIENT":
|
|
2111
|
+
case "INSUFFICIENT_BALANCE":
|
|
2112
|
+
case "IDEMPOTENCY_CONFLICT":
|
|
2113
|
+
case "RATE_LIMITED":
|
|
2114
|
+
case "NETWORK_ERROR":
|
|
2115
|
+
return error;
|
|
2116
|
+
default:
|
|
2117
|
+
return new CapxulError({
|
|
2118
|
+
code: "NETWORK_ERROR",
|
|
2119
|
+
message: error.message,
|
|
2120
|
+
cause: error,
|
|
2121
|
+
details: error.details,
|
|
2122
|
+
operationId: error.operationId,
|
|
2123
|
+
correlationId: error.correlationId,
|
|
2124
|
+
retryable: error.retryable
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/core/transfers.ts
|
|
2130
|
+
function createTransfersClient() {
|
|
2131
|
+
return {
|
|
2132
|
+
create: async () => stub("transfers.create"),
|
|
2133
|
+
retrieve: async () => stub("transfers.retrieve"),
|
|
2134
|
+
list: async () => stub("transfers.list"),
|
|
2135
|
+
confirm: async () => stub("transfers.confirm"),
|
|
2136
|
+
cancel: async () => stub("transfers.cancel")
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function createOrgTransfersClient() {
|
|
2140
|
+
return {
|
|
2141
|
+
create: async () => stub(
|
|
2142
|
+
"organizations.transfers.create"
|
|
2143
|
+
),
|
|
2144
|
+
retrieve: async () => stub("organizations.transfers.retrieve"),
|
|
2145
|
+
list: async () => stub("organizations.transfers.list"),
|
|
2146
|
+
confirm: async () => stub("organizations.transfers.confirm"),
|
|
2147
|
+
cancel: async () => stub("organizations.transfers.cancel")
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
function createWithdrawalsClient(config = {}) {
|
|
2151
|
+
return {
|
|
2152
|
+
create: async (input) => {
|
|
2153
|
+
if (!config.data) {
|
|
2154
|
+
return stub("withdrawals.create");
|
|
2155
|
+
}
|
|
2156
|
+
const [createErr, createdRaw] = await tryCatch(
|
|
2157
|
+
config.data.mutation(api.withdrawals.mutations.create, {
|
|
2158
|
+
amount: input.amount,
|
|
2159
|
+
destination: {
|
|
2160
|
+
externalAccountId: input.destination.externalAccountId
|
|
2161
|
+
},
|
|
2162
|
+
source: input.source,
|
|
2163
|
+
reference: input.reference,
|
|
2164
|
+
idempotencyKey: input.idempotencyKey
|
|
2165
|
+
})
|
|
2166
|
+
);
|
|
2167
|
+
if (createErr) {
|
|
2168
|
+
return [mapCreateError2(fromConvexError(createErr)), null];
|
|
2169
|
+
}
|
|
2170
|
+
const created = createdRaw;
|
|
2171
|
+
if (!created) {
|
|
2172
|
+
return [
|
|
2173
|
+
new CapxulError({
|
|
2174
|
+
code: "NETWORK_ERROR",
|
|
2175
|
+
message: "withdrawals.create returned no withdrawal resource"
|
|
2176
|
+
}),
|
|
2177
|
+
null
|
|
2178
|
+
];
|
|
2179
|
+
}
|
|
2180
|
+
if (created.status !== "processing" || created.operation.status !== "processing") {
|
|
2181
|
+
return [null, created];
|
|
2182
|
+
}
|
|
2183
|
+
if (!config.signer || !config.signing) {
|
|
2184
|
+
return [null, created];
|
|
2185
|
+
}
|
|
2186
|
+
const [signerErr, currentSigner] = await tryCatch(
|
|
2187
|
+
config.data.query(api.safe.queries.getMySignerAddress, {})
|
|
2188
|
+
);
|
|
2189
|
+
if (signerErr) {
|
|
2190
|
+
return await handleSubmissionFailure(
|
|
2191
|
+
{ data: config.data },
|
|
2192
|
+
created.id,
|
|
2193
|
+
mapCreateError2(fromConvexError(signerErr))
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
if (!currentSigner?.address) {
|
|
2197
|
+
return await handleSubmissionFailure(
|
|
2198
|
+
{ data: config.data },
|
|
2199
|
+
created.id,
|
|
2200
|
+
new CapxulError({
|
|
2201
|
+
code: "PERMISSION_DENIED",
|
|
2202
|
+
message: "No signer is registered for the authenticated account.",
|
|
2203
|
+
details: { withdrawalId: created.id }
|
|
2204
|
+
})
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
|
|
2208
|
+
return await handleSubmissionFailure(
|
|
2209
|
+
{ data: config.data },
|
|
2210
|
+
created.id,
|
|
2211
|
+
new CapxulError({
|
|
2212
|
+
code: "PERMISSION_DENIED",
|
|
2213
|
+
message: "Configured signer does not match the authenticated account signer.",
|
|
2214
|
+
details: {
|
|
2215
|
+
withdrawalId: created.id,
|
|
2216
|
+
expectedSignerAddress: currentSigner.address,
|
|
2217
|
+
actualSignerAddress: config.signer.address
|
|
2218
|
+
}
|
|
2219
|
+
})
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
const [prepErr, submission] = await tryCatch(
|
|
2223
|
+
config.data.query(api.withdrawals.queries.prepareSubmission, {
|
|
2224
|
+
withdrawalId: created.id
|
|
2225
|
+
})
|
|
2226
|
+
);
|
|
2227
|
+
if (prepErr) {
|
|
2228
|
+
return await handleSubmissionFailure(
|
|
2229
|
+
{ data: config.data },
|
|
2230
|
+
created.id,
|
|
2231
|
+
mapCreateError2(fromConvexError(prepErr))
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
const destinationAddress = submission?.destinationAddress;
|
|
2235
|
+
if (!submission || !destinationAddress) {
|
|
2236
|
+
return await handleSubmissionFailure(
|
|
2237
|
+
{ data: config.data },
|
|
2238
|
+
created.id,
|
|
2239
|
+
new CapxulError({
|
|
2240
|
+
code: "NETWORK_ERROR",
|
|
2241
|
+
message: "withdrawals.prepareSubmission returned no destination.",
|
|
2242
|
+
details: { withdrawalId: created.id }
|
|
2243
|
+
})
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
const [transferErr, transferOk] = await tryCatch(
|
|
2247
|
+
transferAsOwner(
|
|
2248
|
+
{
|
|
2249
|
+
signer: config.signer,
|
|
2250
|
+
signing: config.signing
|
|
2251
|
+
},
|
|
2252
|
+
{
|
|
2253
|
+
tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
|
|
2254
|
+
recipientAddress: destinationAddress,
|
|
2255
|
+
amount: toTokenUnits(submission.amount.value, 6)
|
|
2256
|
+
}
|
|
2257
|
+
)
|
|
2258
|
+
);
|
|
2259
|
+
if (transferErr) {
|
|
2260
|
+
return await handleSubmissionFailure(
|
|
2261
|
+
{ data: config.data },
|
|
2262
|
+
created.id,
|
|
2263
|
+
mapCreateError2(fromConvexError(transferErr))
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
if (!transferOk.success) {
|
|
2267
|
+
return await handleSubmissionFailure(
|
|
2268
|
+
{ data: config.data },
|
|
2269
|
+
created.id,
|
|
2270
|
+
new CapxulError({
|
|
2271
|
+
code: "NETWORK_ERROR",
|
|
2272
|
+
message: "Bundler submission did not succeed.",
|
|
2273
|
+
details: {
|
|
2274
|
+
withdrawalId: created.id,
|
|
2275
|
+
txHash: transferOk.txHash,
|
|
2276
|
+
userOpHash: transferOk.userOpHash
|
|
2277
|
+
}
|
|
2278
|
+
})
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
const [recordErr] = await tryCatch(
|
|
2282
|
+
config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
|
|
2283
|
+
withdrawalId: created.id,
|
|
2284
|
+
txHash: transferOk.txHash,
|
|
2285
|
+
userOpHash: transferOk.userOpHash
|
|
2286
|
+
})
|
|
2287
|
+
);
|
|
2288
|
+
if (recordErr) {
|
|
2289
|
+
return [
|
|
2290
|
+
new CapxulError({
|
|
2291
|
+
code: "NETWORK_ERROR",
|
|
2292
|
+
message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
|
|
2293
|
+
cause: recordErr,
|
|
2294
|
+
details: {
|
|
2295
|
+
withdrawalId: created.id,
|
|
2296
|
+
txHash: transferOk.txHash,
|
|
2297
|
+
userOpHash: transferOk.userOpHash
|
|
2298
|
+
}
|
|
2299
|
+
}),
|
|
2300
|
+
null
|
|
2301
|
+
];
|
|
2302
|
+
}
|
|
2303
|
+
return [null, created];
|
|
2304
|
+
},
|
|
2305
|
+
retrieve: async (withdrawalId) => {
|
|
2306
|
+
if (!config.data) {
|
|
2307
|
+
return stub("withdrawals.retrieve");
|
|
2308
|
+
}
|
|
2309
|
+
const [err, raw] = await tryCatch(
|
|
2310
|
+
config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
|
|
2311
|
+
);
|
|
2312
|
+
if (err) {
|
|
2313
|
+
return [fromConvexError(err), null];
|
|
2314
|
+
}
|
|
2315
|
+
const withdrawal = raw;
|
|
2316
|
+
if (!withdrawal) {
|
|
2317
|
+
return [
|
|
2318
|
+
new CapxulError({
|
|
2319
|
+
code: "NOT_FOUND",
|
|
2320
|
+
message: `withdrawal ${withdrawalId} not found`
|
|
2321
|
+
}),
|
|
2322
|
+
null
|
|
2323
|
+
];
|
|
2324
|
+
}
|
|
2325
|
+
return [null, withdrawal];
|
|
2326
|
+
},
|
|
2327
|
+
list: async (input) => {
|
|
2328
|
+
if (!config.data) {
|
|
2329
|
+
return stub("withdrawals.list");
|
|
2330
|
+
}
|
|
2331
|
+
const [err, raw] = await tryCatch(
|
|
2332
|
+
config.data.query(api.withdrawals.queries.list, {
|
|
2333
|
+
limit: input?.limit,
|
|
2334
|
+
cursor: input?.cursor
|
|
2335
|
+
})
|
|
2336
|
+
);
|
|
2337
|
+
if (err) {
|
|
2338
|
+
return [fromConvexError(err), null];
|
|
2339
|
+
}
|
|
2340
|
+
return [null, raw];
|
|
2341
|
+
},
|
|
2342
|
+
recordCompleted: async (input) => {
|
|
2343
|
+
if (!config.data) {
|
|
2344
|
+
return stub(
|
|
2345
|
+
"withdrawals.recordCompleted"
|
|
2346
|
+
);
|
|
2347
|
+
}
|
|
2348
|
+
const [err] = await tryCatch(
|
|
2349
|
+
config.data.mutation(api.withdrawals.mutations.recordCompleted, {
|
|
2350
|
+
withdrawalId: input.withdrawalId,
|
|
2351
|
+
txHash: input.txHash
|
|
2352
|
+
})
|
|
2353
|
+
);
|
|
2354
|
+
if (err) {
|
|
2355
|
+
return [
|
|
2356
|
+
mapRecordCompletedError(fromConvexError(err)),
|
|
2357
|
+
null
|
|
2358
|
+
];
|
|
2359
|
+
}
|
|
2360
|
+
return [null, null];
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function createOrgWithdrawalsClient(config = {}) {
|
|
2365
|
+
return {
|
|
2366
|
+
/**
|
|
2367
|
+
* Org-scope create (Withdrawals v1 W2, #465).
|
|
2368
|
+
*
|
|
2369
|
+
* D6 — returns the `processing` row only. No `transferAsOwner`
|
|
2370
|
+
* tail, no `recordSubmitted` call. Org Safe + Zodiac submission
|
|
2371
|
+
* orchestration ships in W3+.
|
|
2372
|
+
*/
|
|
2373
|
+
create: async (input) => {
|
|
2374
|
+
if (!config.data) {
|
|
2375
|
+
return stub(
|
|
2376
|
+
"organizations.withdrawals.create"
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
const [err, raw] = await tryCatch(
|
|
2380
|
+
config.data.mutation(api.withdrawals.mutations.createOrg, {
|
|
2381
|
+
organizationId: input.organizationId,
|
|
2382
|
+
amount: input.amount,
|
|
2383
|
+
destination: {
|
|
2384
|
+
externalAccountId: input.destination.externalAccountId
|
|
2385
|
+
},
|
|
2386
|
+
source: input.source,
|
|
2387
|
+
reference: input.reference,
|
|
2388
|
+
idempotencyKey: input.idempotencyKey
|
|
2389
|
+
})
|
|
2390
|
+
);
|
|
2391
|
+
if (err) {
|
|
2392
|
+
return [mapCreateError2(fromConvexError(err)), null];
|
|
2393
|
+
}
|
|
2394
|
+
const created = raw;
|
|
2395
|
+
if (!created) {
|
|
2396
|
+
return [
|
|
2397
|
+
new CapxulError({
|
|
2398
|
+
code: "NETWORK_ERROR",
|
|
2399
|
+
message: "organizations.withdrawals.create returned no withdrawal resource"
|
|
2400
|
+
}),
|
|
2401
|
+
null
|
|
2402
|
+
];
|
|
1016
2403
|
}
|
|
2404
|
+
return [null, created];
|
|
1017
2405
|
},
|
|
1018
|
-
retrieve: async (
|
|
2406
|
+
retrieve: async (input) => {
|
|
1019
2407
|
if (!config.data) {
|
|
1020
|
-
return stub(
|
|
2408
|
+
return stub(
|
|
2409
|
+
"organizations.withdrawals.retrieve"
|
|
2410
|
+
);
|
|
1021
2411
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1027
|
-
|
|
2412
|
+
const [err, raw] = await tryCatch(
|
|
2413
|
+
config.data.query(api.withdrawals.queries.retrieve, {
|
|
2414
|
+
withdrawalId: input.withdrawalId
|
|
2415
|
+
})
|
|
2416
|
+
);
|
|
2417
|
+
if (err) {
|
|
2418
|
+
return [fromConvexError(err), null];
|
|
2419
|
+
}
|
|
2420
|
+
const withdrawal = raw;
|
|
2421
|
+
if (!withdrawal) {
|
|
2422
|
+
return [
|
|
2423
|
+
new CapxulError({
|
|
1028
2424
|
code: "NOT_FOUND",
|
|
1029
|
-
message: `
|
|
1030
|
-
}),
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
2425
|
+
message: `withdrawal ${input.withdrawalId} not found`
|
|
2426
|
+
}),
|
|
2427
|
+
null
|
|
2428
|
+
];
|
|
2429
|
+
}
|
|
2430
|
+
const ownerCheck = withdrawal.owner;
|
|
2431
|
+
if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
|
|
2432
|
+
return [
|
|
2433
|
+
new CapxulError({
|
|
2434
|
+
code: "NOT_FOUND",
|
|
2435
|
+
message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
|
|
2436
|
+
}),
|
|
2437
|
+
null
|
|
2438
|
+
];
|
|
1035
2439
|
}
|
|
2440
|
+
return [null, withdrawal];
|
|
1036
2441
|
},
|
|
1037
|
-
list: async () =>
|
|
2442
|
+
list: async (input) => {
|
|
2443
|
+
if (!config.data) {
|
|
2444
|
+
return stub(
|
|
2445
|
+
"organizations.withdrawals.list"
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
const [err, raw] = await tryCatch(
|
|
2449
|
+
config.data.query(api.withdrawals.queries.listOrg, {
|
|
2450
|
+
organizationId: input.organizationId,
|
|
2451
|
+
limit: input.limit,
|
|
2452
|
+
cursor: input.cursor
|
|
2453
|
+
})
|
|
2454
|
+
);
|
|
2455
|
+
if (err) {
|
|
2456
|
+
return [fromConvexError(err), null];
|
|
2457
|
+
}
|
|
2458
|
+
return [null, raw];
|
|
2459
|
+
}
|
|
1038
2460
|
};
|
|
1039
2461
|
}
|
|
1040
|
-
function
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
"organizations.payments.create"
|
|
1044
|
-
),
|
|
1045
|
-
retrieve: async () => stub("organizations.payments.retrieve"),
|
|
1046
|
-
list: async () => stub("organizations.payments.list")
|
|
1047
|
-
};
|
|
2462
|
+
async function handleSubmissionFailure(config, withdrawalId, error) {
|
|
2463
|
+
await bestEffortMarkFailed2(config, withdrawalId, error);
|
|
2464
|
+
return [error, null];
|
|
1048
2465
|
}
|
|
1049
|
-
async function
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
2466
|
+
async function bestEffortMarkFailed2(config, withdrawalId, error) {
|
|
2467
|
+
await tryCatch(
|
|
2468
|
+
config.data.mutation(api.withdrawals.mutations.markFailed, {
|
|
2469
|
+
withdrawalId,
|
|
1053
2470
|
errorCode: error.code,
|
|
1054
|
-
errorMessage: error.message
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
} catch {
|
|
1058
|
-
}
|
|
2471
|
+
errorMessage: error.message
|
|
2472
|
+
})
|
|
2473
|
+
);
|
|
1059
2474
|
}
|
|
1060
|
-
function
|
|
2475
|
+
function mapCreateError2(error) {
|
|
1061
2476
|
switch (error.code) {
|
|
1062
2477
|
case "NOT_AUTHENTICATED":
|
|
1063
2478
|
case "PERMISSION_DENIED":
|
|
1064
2479
|
case "INVALID_INPUT":
|
|
1065
|
-
case "INVALID_RECIPIENT":
|
|
1066
2480
|
case "INSUFFICIENT_BALANCE":
|
|
1067
2481
|
case "IDEMPOTENCY_CONFLICT":
|
|
2482
|
+
case "KYC_REQUIRED":
|
|
2483
|
+
case "POLICY_DENIED":
|
|
1068
2484
|
case "RATE_LIMITED":
|
|
1069
2485
|
case "NETWORK_ERROR":
|
|
2486
|
+
case "NOT_FOUND":
|
|
2487
|
+
case "VERIFICATION_REQUIRED":
|
|
2488
|
+
return error;
|
|
2489
|
+
default:
|
|
2490
|
+
return new CapxulError({
|
|
2491
|
+
code: "NETWORK_ERROR",
|
|
2492
|
+
message: error.message,
|
|
2493
|
+
cause: error,
|
|
2494
|
+
details: error.details,
|
|
2495
|
+
operationId: error.operationId,
|
|
2496
|
+
correlationId: error.correlationId,
|
|
2497
|
+
retryable: error.retryable
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
function mapRecordCompletedError(error) {
|
|
2502
|
+
switch (error.code) {
|
|
2503
|
+
case "NOT_AUTHENTICATED":
|
|
2504
|
+
case "PERMISSION_DENIED":
|
|
2505
|
+
case "INVALID_INPUT":
|
|
2506
|
+
case "NOT_FOUND":
|
|
2507
|
+
case "NETWORK_ERROR":
|
|
2508
|
+
case "INTERNAL_ERROR":
|
|
1070
2509
|
return error;
|
|
1071
2510
|
default:
|
|
1072
2511
|
return new CapxulError({
|
|
@@ -1081,508 +2520,785 @@ function mapCreateError(error) {
|
|
|
1081
2520
|
}
|
|
1082
2521
|
}
|
|
1083
2522
|
|
|
1084
|
-
// src/core/
|
|
1085
|
-
function
|
|
2523
|
+
// src/core/webhook-endpoints.ts
|
|
2524
|
+
function createWebhookEndpointsClient() {
|
|
1086
2525
|
return {
|
|
1087
|
-
create: async () => stub(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
2526
|
+
create: async () => stub(
|
|
2527
|
+
"webhookEndpoints.create"
|
|
2528
|
+
),
|
|
2529
|
+
retrieve: async () => stub("webhookEndpoints.retrieve"),
|
|
2530
|
+
list: async () => stub("webhookEndpoints.list"),
|
|
2531
|
+
remove: async () => stub("webhookEndpoints.remove")
|
|
1092
2532
|
};
|
|
1093
2533
|
}
|
|
1094
|
-
|
|
2534
|
+
|
|
2535
|
+
// src/core/webhook-events.ts
|
|
2536
|
+
function createWebhookEventsClient() {
|
|
1095
2537
|
return {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
),
|
|
1099
|
-
retrieve: async () => stub("organizations.transfers.retrieve"),
|
|
1100
|
-
list: async () => stub("organizations.transfers.list"),
|
|
1101
|
-
confirm: async () => stub("organizations.transfers.confirm"),
|
|
1102
|
-
cancel: async () => stub("organizations.transfers.cancel")
|
|
2538
|
+
retrieve: async () => stub("webhookEvents.retrieve"),
|
|
2539
|
+
list: async () => stub("webhookEvents.list")
|
|
1103
2540
|
};
|
|
1104
2541
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
2542
|
+
|
|
2543
|
+
// src/core/organizations.ts
|
|
2544
|
+
function createOrgExternalAccountsClient(config) {
|
|
1107
2545
|
return {
|
|
1108
2546
|
create: async (input) => {
|
|
1109
|
-
if (!config.data) {
|
|
1110
|
-
return stub(
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
if (!config.signer || !config.signing) {
|
|
1144
|
-
return [null, created];
|
|
1145
|
-
}
|
|
1146
|
-
if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
|
|
1147
|
-
throw new CapxulError({
|
|
1148
|
-
code: "INVALID_INPUT",
|
|
1149
|
-
message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
|
|
1150
|
-
details: { field: "destination.externalAccountId" }
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
const currentSigner = await config.data.query(
|
|
1154
|
-
api.safe.queries.getMySignerAddress,
|
|
1155
|
-
{}
|
|
1156
|
-
);
|
|
1157
|
-
if (!currentSigner?.address) {
|
|
1158
|
-
throw new CapxulError({
|
|
1159
|
-
code: "PERMISSION_DENIED",
|
|
1160
|
-
message: "No signer is registered for the authenticated account.",
|
|
1161
|
-
details: { withdrawalId: created.id }
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
|
|
1165
|
-
throw new CapxulError({
|
|
1166
|
-
code: "PERMISSION_DENIED",
|
|
1167
|
-
message: "Configured signer does not match the authenticated account signer.",
|
|
1168
|
-
details: {
|
|
1169
|
-
withdrawalId: created.id,
|
|
1170
|
-
expectedSignerAddress: currentSigner.address,
|
|
1171
|
-
actualSignerAddress: config.signer.address
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
const submission = await config.data.query(
|
|
1176
|
-
api.withdrawals.queries.prepareSubmission,
|
|
1177
|
-
{ withdrawalId: created.id }
|
|
1178
|
-
);
|
|
1179
|
-
if (!submission?.externalAccountId) {
|
|
1180
|
-
throw new CapxulError({
|
|
1181
|
-
code: "NETWORK_ERROR",
|
|
1182
|
-
message: "withdrawals.prepareSubmission returned no destination.",
|
|
1183
|
-
details: { withdrawalId: created.id }
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
const transfer = await transferAsOwner(
|
|
1187
|
-
{
|
|
1188
|
-
signer: config.signer,
|
|
1189
|
-
signing: config.signing
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
|
|
1193
|
-
recipientAddress: submission.externalAccountId,
|
|
1194
|
-
amount: toTokenUnits(submission.amount.value, 6)
|
|
1195
|
-
}
|
|
1196
|
-
);
|
|
1197
|
-
if (!transfer.success) {
|
|
1198
|
-
throw new CapxulError({
|
|
1199
|
-
code: "NETWORK_ERROR",
|
|
1200
|
-
message: "Bundler submission did not succeed.",
|
|
1201
|
-
details: {
|
|
1202
|
-
withdrawalId: created.id,
|
|
1203
|
-
txHash: transfer.txHash,
|
|
1204
|
-
userOpHash: transfer.userOpHash
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
submitted = {
|
|
1209
|
-
txHash: transfer.txHash,
|
|
1210
|
-
userOpHash: transfer.userOpHash
|
|
1211
|
-
};
|
|
1212
|
-
await config.data.mutation(
|
|
1213
|
-
api.withdrawals.mutations.recordSubmitted,
|
|
1214
|
-
{
|
|
1215
|
-
withdrawalId: created.id,
|
|
1216
|
-
txHash: transfer.txHash,
|
|
1217
|
-
userOpHash: transfer.userOpHash
|
|
1218
|
-
}
|
|
1219
|
-
);
|
|
1220
|
-
return [null, created];
|
|
1221
|
-
} catch (cause) {
|
|
1222
|
-
const error = mapCreateError2(fromConvexError(cause));
|
|
1223
|
-
if (created?.id && created.status === "processing" && !submitted) {
|
|
1224
|
-
await bestEffortMarkFailed2({ data: config.data }, created.id, error);
|
|
1225
|
-
}
|
|
1226
|
-
if (submitted && created?.id) {
|
|
1227
|
-
return [
|
|
1228
|
-
new CapxulError({
|
|
1229
|
-
code: "NETWORK_ERROR",
|
|
1230
|
-
message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
|
|
1231
|
-
cause,
|
|
1232
|
-
details: {
|
|
1233
|
-
withdrawalId: created.id,
|
|
1234
|
-
txHash: submitted.txHash,
|
|
1235
|
-
userOpHash: submitted.userOpHash
|
|
1236
|
-
}
|
|
1237
|
-
}),
|
|
1238
|
-
null
|
|
1239
|
-
];
|
|
1240
|
-
}
|
|
1241
|
-
return [error, null];
|
|
2547
|
+
if (!config.data) {
|
|
2548
|
+
return stub(
|
|
2549
|
+
"organizations.externalAccounts.create"
|
|
2550
|
+
);
|
|
2551
|
+
}
|
|
2552
|
+
const [err, raw] = await tryCatch(
|
|
2553
|
+
config.data.mutation(api.externalAccounts.mutations.createOrg, {
|
|
2554
|
+
organizationId: input.organizationId,
|
|
2555
|
+
kind: input.kind,
|
|
2556
|
+
label: input.label,
|
|
2557
|
+
address: input.address,
|
|
2558
|
+
iban: input.iban,
|
|
2559
|
+
bic: input.bic,
|
|
2560
|
+
accountHolder: input.accountHolder,
|
|
2561
|
+
network: input.network,
|
|
2562
|
+
panToken: input.panToken,
|
|
2563
|
+
last4: input.last4
|
|
2564
|
+
})
|
|
2565
|
+
);
|
|
2566
|
+
if (err) {
|
|
2567
|
+
return [
|
|
2568
|
+
fromConvexError(err),
|
|
2569
|
+
null
|
|
2570
|
+
];
|
|
2571
|
+
}
|
|
2572
|
+
if (!raw) {
|
|
2573
|
+
return [
|
|
2574
|
+
new CapxulError({
|
|
2575
|
+
code: "NOT_FOUND",
|
|
2576
|
+
message: "external_account creation returned no resource"
|
|
2577
|
+
}),
|
|
2578
|
+
null
|
|
2579
|
+
];
|
|
1242
2580
|
}
|
|
2581
|
+
return [
|
|
2582
|
+
null,
|
|
2583
|
+
brandExternalAccount(
|
|
2584
|
+
raw
|
|
2585
|
+
)
|
|
2586
|
+
];
|
|
1243
2587
|
},
|
|
1244
|
-
|
|
2588
|
+
list: async (input) => {
|
|
1245
2589
|
if (!config.data) {
|
|
1246
|
-
return stub(
|
|
1247
|
-
|
|
1248
|
-
try {
|
|
1249
|
-
const withdrawal = await config.data.query(
|
|
1250
|
-
api.withdrawals.queries.retrieve,
|
|
1251
|
-
{ withdrawalId }
|
|
2590
|
+
return stub(
|
|
2591
|
+
"organizations.externalAccounts.list"
|
|
1252
2592
|
);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
2593
|
+
}
|
|
2594
|
+
const [err, result] = await tryCatch(
|
|
2595
|
+
config.data.query(api.externalAccounts.queries.listOrg, {
|
|
2596
|
+
organizationId: input.organizationId,
|
|
2597
|
+
limit: input.limit,
|
|
2598
|
+
cursor: input.cursor
|
|
2599
|
+
})
|
|
2600
|
+
);
|
|
2601
|
+
if (err) {
|
|
2602
|
+
return [fromConvexError(err), null];
|
|
2603
|
+
}
|
|
2604
|
+
const branded = result.data.map(
|
|
2605
|
+
(row) => brandExternalAccount(
|
|
2606
|
+
row
|
|
2607
|
+
)
|
|
2608
|
+
);
|
|
2609
|
+
return [
|
|
2610
|
+
null,
|
|
2611
|
+
{
|
|
2612
|
+
object: "list",
|
|
2613
|
+
data: branded,
|
|
2614
|
+
page: result.page
|
|
1261
2615
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
2616
|
+
];
|
|
2617
|
+
},
|
|
2618
|
+
retrieve: async (input) => {
|
|
2619
|
+
if (!config.data) {
|
|
2620
|
+
return stub(
|
|
2621
|
+
"organizations.externalAccounts.retrieve"
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
const [err, raw] = await tryCatch(
|
|
2625
|
+
config.data.query(api.externalAccounts.queries.retrieveOrg, {
|
|
2626
|
+
organizationId: input.organizationId,
|
|
2627
|
+
externalAccountId: input.externalAccountId
|
|
2628
|
+
})
|
|
2629
|
+
);
|
|
2630
|
+
if (err) {
|
|
1264
2631
|
return [
|
|
1265
|
-
fromConvexError(
|
|
2632
|
+
fromConvexError(err),
|
|
2633
|
+
null
|
|
2634
|
+
];
|
|
2635
|
+
}
|
|
2636
|
+
if (!raw) {
|
|
2637
|
+
return [
|
|
2638
|
+
new CapxulError({
|
|
2639
|
+
code: "NOT_FOUND",
|
|
2640
|
+
message: `external_account ${input.externalAccountId} not found`
|
|
2641
|
+
}),
|
|
1266
2642
|
null
|
|
1267
2643
|
];
|
|
1268
2644
|
}
|
|
2645
|
+
return [
|
|
2646
|
+
null,
|
|
2647
|
+
brandExternalAccount(
|
|
2648
|
+
raw
|
|
2649
|
+
)
|
|
2650
|
+
];
|
|
1269
2651
|
},
|
|
1270
|
-
|
|
2652
|
+
remove: async (input) => {
|
|
1271
2653
|
if (!config.data) {
|
|
1272
|
-
return stub(
|
|
1273
|
-
|
|
1274
|
-
try {
|
|
1275
|
-
const result = await config.data.query(
|
|
1276
|
-
api.withdrawals.queries.list,
|
|
1277
|
-
{
|
|
1278
|
-
limit: input?.limit,
|
|
1279
|
-
cursor: input?.cursor
|
|
1280
|
-
}
|
|
2654
|
+
return stub(
|
|
2655
|
+
"organizations.externalAccounts.remove"
|
|
1281
2656
|
);
|
|
1282
|
-
|
|
1283
|
-
|
|
2657
|
+
}
|
|
2658
|
+
const [err] = await tryCatch(
|
|
2659
|
+
config.data.mutation(api.externalAccounts.mutations.removeOrg, {
|
|
2660
|
+
organizationId: input.organizationId,
|
|
2661
|
+
externalAccountId: input.externalAccountId
|
|
2662
|
+
})
|
|
2663
|
+
);
|
|
2664
|
+
if (err) {
|
|
1284
2665
|
return [
|
|
1285
|
-
fromConvexError(
|
|
2666
|
+
fromConvexError(err),
|
|
1286
2667
|
null
|
|
1287
2668
|
];
|
|
1288
2669
|
}
|
|
2670
|
+
return [null, void 0];
|
|
1289
2671
|
}
|
|
1290
2672
|
};
|
|
1291
2673
|
}
|
|
1292
|
-
function
|
|
2674
|
+
function createOrgSubAccountsClient(config) {
|
|
1293
2675
|
return {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
2676
|
+
create: async (input) => {
|
|
2677
|
+
if (!config.data) {
|
|
2678
|
+
return stub(
|
|
2679
|
+
"organizations.subAccounts.create"
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
const [err, raw] = await tryCatch(
|
|
2683
|
+
config.data.mutation(api.subAccounts.mutations.create, {
|
|
2684
|
+
parent: {
|
|
2685
|
+
kind: "organization",
|
|
2686
|
+
id: input.organizationId
|
|
2687
|
+
},
|
|
2688
|
+
name: input.name,
|
|
2689
|
+
purpose: input.purpose
|
|
2690
|
+
})
|
|
2691
|
+
);
|
|
2692
|
+
if (err) {
|
|
2693
|
+
return [
|
|
2694
|
+
fromConvexError(err),
|
|
2695
|
+
null
|
|
2696
|
+
];
|
|
2697
|
+
}
|
|
2698
|
+
if (!raw) {
|
|
2699
|
+
return [
|
|
2700
|
+
new CapxulError({
|
|
2701
|
+
code: "NOT_FOUND",
|
|
2702
|
+
message: "sub_account creation returned no resource"
|
|
2703
|
+
}),
|
|
2704
|
+
null
|
|
2705
|
+
];
|
|
2706
|
+
}
|
|
2707
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2708
|
+
if (brandErr) {
|
|
2709
|
+
return [
|
|
2710
|
+
brandErr,
|
|
2711
|
+
null
|
|
2712
|
+
];
|
|
2713
|
+
}
|
|
2714
|
+
return [null, branded];
|
|
2715
|
+
},
|
|
2716
|
+
list: async (input) => {
|
|
2717
|
+
if (!config.data) {
|
|
2718
|
+
return stub(
|
|
2719
|
+
"organizations.subAccounts.list"
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
const [err, rows] = await tryCatch(
|
|
2723
|
+
config.data.query(api.subAccounts.queries.listByOrganization, {
|
|
2724
|
+
organizationId: input.organizationId
|
|
2725
|
+
})
|
|
2726
|
+
);
|
|
2727
|
+
if (err) {
|
|
2728
|
+
return [
|
|
2729
|
+
fromConvexError(err),
|
|
2730
|
+
null
|
|
2731
|
+
];
|
|
2732
|
+
}
|
|
2733
|
+
const branded = [];
|
|
2734
|
+
for (const row of rows) {
|
|
2735
|
+
const [brandErr, value] = tryBrandSubAccount(row);
|
|
2736
|
+
if (brandErr) {
|
|
2737
|
+
return [
|
|
2738
|
+
brandErr,
|
|
2739
|
+
null
|
|
2740
|
+
];
|
|
2741
|
+
}
|
|
2742
|
+
branded.push(value);
|
|
2743
|
+
}
|
|
2744
|
+
return [
|
|
2745
|
+
null,
|
|
2746
|
+
{
|
|
2747
|
+
object: "list",
|
|
2748
|
+
data: branded,
|
|
2749
|
+
page: { hasMore: false }
|
|
2750
|
+
}
|
|
2751
|
+
];
|
|
2752
|
+
},
|
|
1300
2753
|
retrieve: async (input) => {
|
|
1301
2754
|
if (!config.data) {
|
|
1302
2755
|
return stub(
|
|
1303
|
-
"organizations.
|
|
2756
|
+
"organizations.subAccounts.retrieve"
|
|
1304
2757
|
);
|
|
1305
2758
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
2759
|
+
const [err, raw] = await tryCatch(
|
|
2760
|
+
config.data.query(api.subAccounts.queries.retrieve, {
|
|
2761
|
+
subAccountId: input.subAccountId
|
|
2762
|
+
})
|
|
2763
|
+
);
|
|
2764
|
+
if (err) {
|
|
2765
|
+
return [
|
|
2766
|
+
fromConvexError(err),
|
|
2767
|
+
null
|
|
2768
|
+
];
|
|
2769
|
+
}
|
|
2770
|
+
if (!raw) {
|
|
2771
|
+
return [
|
|
2772
|
+
new CapxulError({
|
|
2773
|
+
code: "NOT_FOUND",
|
|
2774
|
+
message: `sub_account ${input.subAccountId} not found`
|
|
2775
|
+
}),
|
|
2776
|
+
null
|
|
2777
|
+
];
|
|
2778
|
+
}
|
|
2779
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2780
|
+
if (brandErr) {
|
|
2781
|
+
return [
|
|
2782
|
+
brandErr,
|
|
2783
|
+
null
|
|
2784
|
+
];
|
|
2785
|
+
}
|
|
2786
|
+
return [null, branded];
|
|
2787
|
+
},
|
|
2788
|
+
remove: async (input) => {
|
|
2789
|
+
if (!config.data) {
|
|
2790
|
+
return stub(
|
|
2791
|
+
"organizations.subAccounts.remove"
|
|
1310
2792
|
);
|
|
1311
|
-
|
|
2793
|
+
}
|
|
2794
|
+
const [err, raw] = await tryCatch(
|
|
2795
|
+
config.data.mutation(api.subAccounts.mutations.archive, {
|
|
2796
|
+
subAccountId: input.subAccountId
|
|
2797
|
+
})
|
|
2798
|
+
);
|
|
2799
|
+
if (err) {
|
|
2800
|
+
return [
|
|
2801
|
+
fromConvexError(err),
|
|
2802
|
+
null
|
|
2803
|
+
];
|
|
2804
|
+
}
|
|
2805
|
+
if (!raw) {
|
|
2806
|
+
return [
|
|
2807
|
+
new CapxulError({
|
|
2808
|
+
code: "NOT_FOUND",
|
|
2809
|
+
message: `sub_account ${input.subAccountId} not found`
|
|
2810
|
+
}),
|
|
2811
|
+
null
|
|
2812
|
+
];
|
|
2813
|
+
}
|
|
2814
|
+
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
2815
|
+
if (brandErr) {
|
|
2816
|
+
return [
|
|
2817
|
+
brandErr,
|
|
2818
|
+
null
|
|
2819
|
+
];
|
|
2820
|
+
}
|
|
2821
|
+
return [null, branded];
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
function createOrganizationsClient(config = {}) {
|
|
2826
|
+
return {
|
|
2827
|
+
create: async () => stub("organizations.create"),
|
|
2828
|
+
retrieve: async () => stub("organizations.retrieve"),
|
|
2829
|
+
list: async () => stub("organizations.list"),
|
|
2830
|
+
update: async () => stub("organizations.update"),
|
|
2831
|
+
safes: {
|
|
2832
|
+
retrieve: async (input) => {
|
|
2833
|
+
if (!config.data) {
|
|
2834
|
+
return stub("organizations.safes.retrieve");
|
|
2835
|
+
}
|
|
2836
|
+
try {
|
|
2837
|
+
const safe = await config.data.query(
|
|
2838
|
+
api.safe.queries.retrieveOrganizationSafe,
|
|
2839
|
+
input
|
|
2840
|
+
);
|
|
2841
|
+
if (!safe) {
|
|
2842
|
+
return [
|
|
2843
|
+
new CapxulError({
|
|
2844
|
+
code: "NOT_FOUND",
|
|
2845
|
+
message: `safe ${input.safeId} not found`
|
|
2846
|
+
}),
|
|
2847
|
+
null
|
|
2848
|
+
];
|
|
2849
|
+
}
|
|
2850
|
+
return [null, safe];
|
|
2851
|
+
} catch (cause) {
|
|
1312
2852
|
return [
|
|
1313
|
-
|
|
1314
|
-
code: "NOT_FOUND",
|
|
1315
|
-
message: `withdrawal ${input.withdrawalId} not found`
|
|
1316
|
-
}),
|
|
2853
|
+
fromConvexError(cause),
|
|
1317
2854
|
null
|
|
1318
2855
|
];
|
|
1319
2856
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
treasury: {
|
|
2860
|
+
retrieve: async () => stub("organizations.treasury.retrieve")
|
|
2861
|
+
},
|
|
2862
|
+
members: {
|
|
2863
|
+
list: async () => stub("organizations.members.list"),
|
|
2864
|
+
retrieve: async () => stub("organizations.members.retrieve"),
|
|
2865
|
+
invite: async () => stub("organizations.members.invite"),
|
|
2866
|
+
updateRole: async () => stub("organizations.members.updateRole"),
|
|
2867
|
+
remove: async () => stub("organizations.members.remove")
|
|
2868
|
+
},
|
|
2869
|
+
apiKeys: createApiKeysClient(),
|
|
2870
|
+
kybProfile: {
|
|
2871
|
+
start: async () => stub("organizations.kybProfile.start"),
|
|
2872
|
+
retrieve: async () => stub("organizations.kybProfile.retrieve")
|
|
2873
|
+
},
|
|
2874
|
+
subAccounts: createOrgSubAccountsClient(config),
|
|
2875
|
+
externalAccounts: createOrgExternalAccountsClient(config),
|
|
2876
|
+
balanceLedger: {
|
|
2877
|
+
list: async () => stub(
|
|
2878
|
+
"organizations.balanceLedger.list"
|
|
2879
|
+
),
|
|
2880
|
+
retrieve: async () => stub(
|
|
2881
|
+
"organizations.balanceLedger.retrieve"
|
|
2882
|
+
)
|
|
2883
|
+
},
|
|
2884
|
+
payments: createOrgPaymentsClient(),
|
|
2885
|
+
transfers: createOrgTransfersClient(),
|
|
2886
|
+
withdrawals: createOrgWithdrawalsClient(config),
|
|
2887
|
+
documents: createOrgDocumentsClient(),
|
|
2888
|
+
webhookEndpoints: createWebhookEndpointsClient(),
|
|
2889
|
+
webhookEvents: createWebhookEventsClient()
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// src/core/token-transfers.ts
|
|
2894
|
+
var toTokenTransferId = (raw) => {
|
|
2895
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
2896
|
+
throw new Error(
|
|
2897
|
+
`Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
return raw;
|
|
2901
|
+
};
|
|
2902
|
+
function brandRow(row) {
|
|
2903
|
+
return {
|
|
2904
|
+
...row,
|
|
2905
|
+
id: toTokenTransferId(row.id)
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
function createTokenTransfersClient(config = {}) {
|
|
2909
|
+
return {
|
|
2910
|
+
list: async (input) => {
|
|
2911
|
+
if (!config.data) {
|
|
2912
|
+
return stub("tokenTransfers.list");
|
|
2913
|
+
}
|
|
2914
|
+
try {
|
|
2915
|
+
const raw = await config.data.query(
|
|
2916
|
+
api.tokenTransfers.queries.list,
|
|
2917
|
+
{
|
|
2918
|
+
limit: input?.limit,
|
|
2919
|
+
cursor: input?.cursor,
|
|
2920
|
+
direction: input?.direction
|
|
2921
|
+
}
|
|
2922
|
+
);
|
|
2923
|
+
if (!raw) {
|
|
1322
2924
|
return [
|
|
1323
2925
|
new CapxulError({
|
|
1324
|
-
code: "
|
|
1325
|
-
message:
|
|
2926
|
+
code: "NOT_AUTHENTICATED",
|
|
2927
|
+
message: "tokenTransfers.list requires an authenticated session."
|
|
1326
2928
|
}),
|
|
1327
2929
|
null
|
|
1328
2930
|
];
|
|
1329
2931
|
}
|
|
1330
|
-
return [null, withdrawal];
|
|
1331
|
-
} catch (cause) {
|
|
1332
2932
|
return [
|
|
1333
|
-
|
|
1334
|
-
|
|
2933
|
+
null,
|
|
2934
|
+
{
|
|
2935
|
+
object: "list",
|
|
2936
|
+
data: raw.items.map(brandRow),
|
|
2937
|
+
page: {
|
|
2938
|
+
hasMore: raw.hasMore,
|
|
2939
|
+
nextCursor: raw.nextCursor
|
|
2940
|
+
},
|
|
2941
|
+
displayCurrency: raw.displayCurrency
|
|
2942
|
+
}
|
|
1335
2943
|
];
|
|
2944
|
+
} catch (cause) {
|
|
2945
|
+
return [fromConvexError(cause), null];
|
|
1336
2946
|
}
|
|
1337
2947
|
},
|
|
1338
|
-
|
|
1339
|
-
if (!config.data) {
|
|
1340
|
-
return stub(
|
|
1341
|
-
"organizations.withdrawals.list"
|
|
1342
|
-
);
|
|
2948
|
+
retrieve: async (input) => {
|
|
2949
|
+
if (!config.data) {
|
|
2950
|
+
return stub("tokenTransfers.retrieve");
|
|
1343
2951
|
}
|
|
1344
2952
|
try {
|
|
1345
|
-
const
|
|
1346
|
-
api.
|
|
2953
|
+
const raw = await config.data.query(
|
|
2954
|
+
api.tokenTransfers.queries.getByTxLogIndex,
|
|
1347
2955
|
{
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
2956
|
+
txHash: input.txHash,
|
|
2957
|
+
logIndex: input.logIndex,
|
|
2958
|
+
chainId: input.chainId
|
|
1351
2959
|
}
|
|
1352
2960
|
);
|
|
1353
|
-
|
|
2961
|
+
if (!raw) {
|
|
2962
|
+
return [
|
|
2963
|
+
new CapxulError({
|
|
2964
|
+
code: "NOT_FOUND",
|
|
2965
|
+
message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
|
|
2966
|
+
details: {
|
|
2967
|
+
txHash: input.txHash,
|
|
2968
|
+
logIndex: input.logIndex,
|
|
2969
|
+
chainId: input.chainId
|
|
2970
|
+
}
|
|
2971
|
+
}),
|
|
2972
|
+
null
|
|
2973
|
+
];
|
|
2974
|
+
}
|
|
2975
|
+
return [null, brandRow(raw)];
|
|
1354
2976
|
} catch (cause) {
|
|
1355
|
-
return [
|
|
1356
|
-
fromConvexError(cause),
|
|
1357
|
-
null
|
|
1358
|
-
];
|
|
2977
|
+
return [fromConvexError(cause), null];
|
|
1359
2978
|
}
|
|
1360
2979
|
}
|
|
1361
2980
|
};
|
|
1362
2981
|
}
|
|
1363
|
-
async function bestEffortMarkFailed2(config, withdrawalId, error) {
|
|
1364
|
-
try {
|
|
1365
|
-
await config.data.mutation(api.withdrawals.mutations.markFailed, {
|
|
1366
|
-
withdrawalId,
|
|
1367
|
-
errorCode: error.code,
|
|
1368
|
-
errorMessage: error.message
|
|
1369
|
-
});
|
|
1370
|
-
} catch {
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
function mapCreateError2(error) {
|
|
1374
|
-
switch (error.code) {
|
|
1375
|
-
case "NOT_AUTHENTICATED":
|
|
1376
|
-
case "PERMISSION_DENIED":
|
|
1377
|
-
case "INVALID_INPUT":
|
|
1378
|
-
case "INSUFFICIENT_BALANCE":
|
|
1379
|
-
case "IDEMPOTENCY_CONFLICT":
|
|
1380
|
-
case "KYC_REQUIRED":
|
|
1381
|
-
case "POLICY_DENIED":
|
|
1382
|
-
case "RATE_LIMITED":
|
|
1383
|
-
case "NETWORK_ERROR":
|
|
1384
|
-
return error;
|
|
1385
|
-
default:
|
|
1386
|
-
return new CapxulError({
|
|
1387
|
-
code: "NETWORK_ERROR",
|
|
1388
|
-
message: error.message,
|
|
1389
|
-
cause: error,
|
|
1390
|
-
details: error.details,
|
|
1391
|
-
operationId: error.operationId,
|
|
1392
|
-
correlationId: error.correlationId,
|
|
1393
|
-
retryable: error.retryable
|
|
1394
|
-
});
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
2982
|
|
|
1398
|
-
// src/core/
|
|
1399
|
-
function
|
|
2983
|
+
// src/core/virtual-accounts.ts
|
|
2984
|
+
function createVirtualAccountsClient() {
|
|
1400
2985
|
return {
|
|
1401
|
-
create: async () => stub(
|
|
1402
|
-
|
|
1403
|
-
),
|
|
1404
|
-
|
|
1405
|
-
list: async () => stub("webhookEndpoints.list"),
|
|
1406
|
-
remove: async () => stub("webhookEndpoints.remove")
|
|
2986
|
+
create: async () => stub("virtualAccounts.create"),
|
|
2987
|
+
retrieve: async () => stub("virtualAccounts.retrieve"),
|
|
2988
|
+
list: async () => stub("virtualAccounts.list"),
|
|
2989
|
+
remove: async () => stub("virtualAccounts.remove")
|
|
1407
2990
|
};
|
|
1408
2991
|
}
|
|
1409
2992
|
|
|
1410
|
-
// src/core/
|
|
1411
|
-
function
|
|
2993
|
+
// src/core/virtual-cards.ts
|
|
2994
|
+
function createVirtualCardsClient() {
|
|
1412
2995
|
return {
|
|
1413
|
-
|
|
1414
|
-
|
|
2996
|
+
create: async () => stub("virtualCards.create"),
|
|
2997
|
+
retrieve: async () => stub("virtualCards.retrieve"),
|
|
2998
|
+
list: async () => stub("virtualCards.list"),
|
|
2999
|
+
freeze: async () => stub("virtualCards.freeze"),
|
|
3000
|
+
unfreeze: async () => stub("virtualCards.unfreeze"),
|
|
3001
|
+
cancel: async () => stub("virtualCards.cancel")
|
|
1415
3002
|
};
|
|
1416
3003
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
3004
|
+
function createAuthFlowMachine(client) {
|
|
3005
|
+
return xstate.setup({
|
|
3006
|
+
types: {},
|
|
3007
|
+
actors: {
|
|
3008
|
+
// XState v5's `fromPromise` injects an `AbortSignal` that aborts
|
|
3009
|
+
// when the actor is stopped (parent transition fires, machine is
|
|
3010
|
+
// disposed, etc.). Plumbing it through `client.auth.sendOtp` /
|
|
3011
|
+
// `client.auth.verifyOtp` makes the in-flight HTTP request
|
|
3012
|
+
// cancellable: stale responses can't race a state machine
|
|
3013
|
+
// that's already moved on. See PR #406 S5.
|
|
3014
|
+
sendOtp: xstate.fromPromise(async ({ input, signal }) => {
|
|
3015
|
+
const [error] = await client.auth.sendOtp(
|
|
3016
|
+
{ email: input.email },
|
|
3017
|
+
{ signal }
|
|
3018
|
+
);
|
|
3019
|
+
if (error) throw error;
|
|
3020
|
+
}),
|
|
3021
|
+
verifyOtp: xstate.fromPromise(
|
|
3022
|
+
async ({ input, signal }) => {
|
|
3023
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
3024
|
+
{
|
|
3025
|
+
email: input.email,
|
|
3026
|
+
otp: input.code
|
|
3027
|
+
},
|
|
3028
|
+
{ signal }
|
|
3029
|
+
);
|
|
3030
|
+
if (error) throw error;
|
|
3031
|
+
if (result.kind === "bootstrap_required") {
|
|
3032
|
+
throw new CapxulError({
|
|
3033
|
+
code: "ACTION_REQUIRED",
|
|
3034
|
+
message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
|
|
3035
|
+
details: { reason: result.reason }
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
return result.session;
|
|
3039
|
+
}
|
|
3040
|
+
),
|
|
3041
|
+
signOut: xstate.fromPromise(async () => {
|
|
3042
|
+
const [error] = await client.auth.signOut();
|
|
3043
|
+
if (error) throw error;
|
|
3044
|
+
})
|
|
3045
|
+
},
|
|
3046
|
+
actions: {
|
|
3047
|
+
trackOtpRequested: ({ context }) => {
|
|
3048
|
+
if (!context.email) return;
|
|
3049
|
+
track("auth_otp_requested", {
|
|
3050
|
+
email_domain: emailDomain(context.email)
|
|
3051
|
+
});
|
|
3052
|
+
},
|
|
3053
|
+
trackOtpFailed: ({ event }) => {
|
|
3054
|
+
const error = errorFromEvent(event);
|
|
3055
|
+
track("auth_failed", {
|
|
3056
|
+
auth_type: "email_otp",
|
|
3057
|
+
reason: error.code
|
|
3058
|
+
});
|
|
3059
|
+
},
|
|
3060
|
+
trackTimeoutFailed: () => {
|
|
3061
|
+
track("auth_failed", {
|
|
3062
|
+
auth_type: "email_otp",
|
|
3063
|
+
reason: "timeout"
|
|
3064
|
+
});
|
|
3065
|
+
},
|
|
3066
|
+
trackVerified: () => {
|
|
3067
|
+
track("auth_verified", { auth_type: "email_otp" });
|
|
3068
|
+
},
|
|
3069
|
+
identifyAndTrack: ({ context }) => {
|
|
3070
|
+
if (!context.session) return;
|
|
3071
|
+
identify(context.session.authUserId, {
|
|
3072
|
+
email_domain: emailDomain(context.session.email)
|
|
3073
|
+
});
|
|
3074
|
+
track("auth_identified", {
|
|
3075
|
+
email_domain: emailDomain(context.session.email)
|
|
3076
|
+
});
|
|
3077
|
+
},
|
|
3078
|
+
trackSignedOut: () => {
|
|
3079
|
+
track("auth_signed_out");
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}).createMachine({
|
|
3083
|
+
id: "auth",
|
|
3084
|
+
initial: "idle",
|
|
3085
|
+
context: { email: null, session: null, error: null },
|
|
3086
|
+
states: {
|
|
3087
|
+
idle: {
|
|
3088
|
+
on: {
|
|
3089
|
+
REQUEST_OTP: {
|
|
3090
|
+
target: "sending_otp",
|
|
3091
|
+
actions: xstate.assign({
|
|
3092
|
+
email: ({ event }) => event.email,
|
|
3093
|
+
error: () => null
|
|
3094
|
+
})
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
},
|
|
3098
|
+
sending_otp: {
|
|
3099
|
+
invoke: {
|
|
3100
|
+
src: "sendOtp",
|
|
3101
|
+
input: ({ context }) => ({ email: requireEmail(context) }),
|
|
3102
|
+
onDone: {
|
|
3103
|
+
target: "otp_requested",
|
|
3104
|
+
actions: ["trackOtpRequested"]
|
|
3105
|
+
},
|
|
3106
|
+
onError: {
|
|
3107
|
+
target: "error",
|
|
3108
|
+
actions: [
|
|
3109
|
+
xstate.assign({ error: ({ event }) => errorFromEvent(event) }),
|
|
3110
|
+
"trackOtpFailed"
|
|
3111
|
+
]
|
|
3112
|
+
}
|
|
3113
|
+
},
|
|
3114
|
+
after: {
|
|
3115
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3116
|
+
target: "error",
|
|
3117
|
+
actions: [
|
|
3118
|
+
xstate.assign({
|
|
3119
|
+
error: () => timeoutError("sending_otp")
|
|
3120
|
+
}),
|
|
3121
|
+
"trackTimeoutFailed"
|
|
3122
|
+
]
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
},
|
|
3126
|
+
otp_requested: {
|
|
3127
|
+
on: {
|
|
3128
|
+
VERIFY: { target: "verifying" },
|
|
3129
|
+
RESET: {
|
|
3130
|
+
target: "idle",
|
|
3131
|
+
actions: xstate.assign({ email: () => null, error: () => null })
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
},
|
|
3135
|
+
verifying: {
|
|
3136
|
+
invoke: {
|
|
3137
|
+
src: "verifyOtp",
|
|
3138
|
+
input: ({ context, event }) => ({
|
|
3139
|
+
email: requireEmail(context),
|
|
3140
|
+
code: requireCodeFromEvent(event)
|
|
3141
|
+
}),
|
|
3142
|
+
onDone: {
|
|
3143
|
+
target: "authenticated",
|
|
3144
|
+
actions: [
|
|
3145
|
+
// Scrub the duplicate `context.email` (input value
|
|
3146
|
+
// captured during sendOtp) since the verified
|
|
3147
|
+
// `session.email` is now the canonical source
|
|
3148
|
+
// post-authentication. The session's email is
|
|
3149
|
+
// intentionally retained — it's the auth result, not
|
|
3150
|
+
// lingering input. See PR #406 S2.
|
|
3151
|
+
xstate.assign({
|
|
3152
|
+
session: ({ event }) => event.output,
|
|
3153
|
+
email: () => null
|
|
3154
|
+
}),
|
|
3155
|
+
"trackVerified",
|
|
3156
|
+
"identifyAndTrack"
|
|
3157
|
+
]
|
|
3158
|
+
},
|
|
3159
|
+
onError: {
|
|
3160
|
+
target: "error",
|
|
3161
|
+
actions: [
|
|
3162
|
+
xstate.assign({ error: ({ event }) => errorFromEvent(event) }),
|
|
3163
|
+
"trackOtpFailed"
|
|
3164
|
+
]
|
|
3165
|
+
}
|
|
3166
|
+
},
|
|
3167
|
+
after: {
|
|
3168
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3169
|
+
target: "error",
|
|
3170
|
+
actions: [
|
|
3171
|
+
xstate.assign({
|
|
3172
|
+
error: () => timeoutError("verifying")
|
|
3173
|
+
}),
|
|
3174
|
+
"trackTimeoutFailed"
|
|
3175
|
+
]
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
},
|
|
3179
|
+
authenticated: {
|
|
3180
|
+
on: {
|
|
3181
|
+
SIGN_OUT: { target: "signing_out" }
|
|
1429
3182
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
3183
|
+
},
|
|
3184
|
+
signing_out: {
|
|
3185
|
+
invoke: {
|
|
3186
|
+
src: "signOut",
|
|
3187
|
+
onDone: {
|
|
3188
|
+
target: "idle",
|
|
3189
|
+
actions: [
|
|
3190
|
+
xstate.assign({
|
|
3191
|
+
session: () => null,
|
|
3192
|
+
email: () => null,
|
|
3193
|
+
error: () => null
|
|
1440
3194
|
}),
|
|
1441
|
-
|
|
1442
|
-
]
|
|
3195
|
+
"trackSignedOut"
|
|
3196
|
+
]
|
|
3197
|
+
},
|
|
3198
|
+
onError: {
|
|
3199
|
+
target: "error",
|
|
3200
|
+
actions: xstate.assign({ error: ({ event }) => errorFromEvent(event) })
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
},
|
|
3204
|
+
error: {
|
|
3205
|
+
on: {
|
|
3206
|
+
RESET: {
|
|
3207
|
+
target: "idle",
|
|
3208
|
+
actions: xstate.assign({ error: () => null })
|
|
1443
3209
|
}
|
|
1444
|
-
return [null, safe];
|
|
1445
|
-
} catch (cause) {
|
|
1446
|
-
return [
|
|
1447
|
-
fromConvexError(cause),
|
|
1448
|
-
null
|
|
1449
|
-
];
|
|
1450
3210
|
}
|
|
1451
3211
|
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
retrieve: async () => stub("organizations.treasury.retrieve")
|
|
1455
|
-
},
|
|
1456
|
-
members: {
|
|
1457
|
-
list: async () => stub("organizations.members.list"),
|
|
1458
|
-
retrieve: async () => stub("organizations.members.retrieve"),
|
|
1459
|
-
invite: async () => stub("organizations.members.invite"),
|
|
1460
|
-
updateRole: async () => stub("organizations.members.updateRole"),
|
|
1461
|
-
remove: async () => stub("organizations.members.remove")
|
|
1462
|
-
},
|
|
1463
|
-
apiKeys: createApiKeysClient(),
|
|
1464
|
-
kybProfile: {
|
|
1465
|
-
start: async () => stub("organizations.kybProfile.start"),
|
|
1466
|
-
retrieve: async () => stub("organizations.kybProfile.retrieve")
|
|
1467
|
-
},
|
|
1468
|
-
subAccounts: {
|
|
1469
|
-
create: async () => stub("organizations.subAccounts.create"),
|
|
1470
|
-
list: async () => stub("organizations.subAccounts.list"),
|
|
1471
|
-
retrieve: async () => stub("organizations.subAccounts.retrieve"),
|
|
1472
|
-
remove: async () => stub("organizations.subAccounts.remove")
|
|
1473
|
-
},
|
|
1474
|
-
externalAccounts: {
|
|
1475
|
-
create: async () => stub(
|
|
1476
|
-
"organizations.externalAccounts.create"
|
|
1477
|
-
),
|
|
1478
|
-
list: async () => stub(
|
|
1479
|
-
"organizations.externalAccounts.list"
|
|
1480
|
-
),
|
|
1481
|
-
retrieve: async () => stub(
|
|
1482
|
-
"organizations.externalAccounts.retrieve"
|
|
1483
|
-
),
|
|
1484
|
-
remove: async () => stub("organizations.externalAccounts.remove")
|
|
1485
|
-
},
|
|
1486
|
-
balanceLedger: {
|
|
1487
|
-
list: async () => stub(
|
|
1488
|
-
"organizations.balanceLedger.list"
|
|
1489
|
-
),
|
|
1490
|
-
retrieve: async () => stub(
|
|
1491
|
-
"organizations.balanceLedger.retrieve"
|
|
1492
|
-
)
|
|
1493
|
-
},
|
|
1494
|
-
payments: createOrgPaymentsClient(),
|
|
1495
|
-
transfers: createOrgTransfersClient(),
|
|
1496
|
-
withdrawals: createOrgWithdrawalsClient(config),
|
|
1497
|
-
documents: createOrgDocumentsClient(),
|
|
1498
|
-
webhookEndpoints: createWebhookEndpointsClient(),
|
|
1499
|
-
webhookEvents: createWebhookEventsClient()
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
// src/core/sub-accounts.ts
|
|
1504
|
-
function createSubAccountsClient() {
|
|
1505
|
-
return {
|
|
1506
|
-
retrieve: async () => stub("subAccounts.retrieve"),
|
|
1507
|
-
remove: async () => stub("subAccounts.remove")
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// src/core/virtual-accounts.ts
|
|
1512
|
-
function createVirtualAccountsClient() {
|
|
1513
|
-
return {
|
|
1514
|
-
create: async () => stub("virtualAccounts.create"),
|
|
1515
|
-
retrieve: async () => stub("virtualAccounts.retrieve"),
|
|
1516
|
-
list: async () => stub("virtualAccounts.list"),
|
|
1517
|
-
remove: async () => stub("virtualAccounts.remove")
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// src/core/virtual-cards.ts
|
|
1522
|
-
function createVirtualCardsClient() {
|
|
1523
|
-
return {
|
|
1524
|
-
create: async () => stub("virtualCards.create"),
|
|
1525
|
-
retrieve: async () => stub("virtualCards.retrieve"),
|
|
1526
|
-
list: async () => stub("virtualCards.list"),
|
|
1527
|
-
freeze: async () => stub("virtualCards.freeze"),
|
|
1528
|
-
unfreeze: async () => stub("virtualCards.unfreeze"),
|
|
1529
|
-
cancel: async () => stub("virtualCards.cancel")
|
|
1530
|
-
};
|
|
3212
|
+
}
|
|
3213
|
+
});
|
|
1531
3214
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
3215
|
+
function requireEmail(context) {
|
|
3216
|
+
if (!context.email) {
|
|
3217
|
+
throw Errors.invalidInput(
|
|
3218
|
+
"email",
|
|
3219
|
+
"Auth flow advanced without an email captured in context."
|
|
3220
|
+
);
|
|
1537
3221
|
}
|
|
1538
|
-
return
|
|
3222
|
+
return context.email;
|
|
1539
3223
|
}
|
|
1540
|
-
function
|
|
1541
|
-
if (
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
|
|
1547
|
-
process.stderr.write(`${line}
|
|
1548
|
-
`);
|
|
3224
|
+
function requireCodeFromEvent(event) {
|
|
3225
|
+
if (event.type !== "VERIFY") {
|
|
3226
|
+
throw Errors.invalidInput(
|
|
3227
|
+
"code",
|
|
3228
|
+
`Auth flow's verifying state requires a VERIFY event, got ${event.type}.`
|
|
3229
|
+
);
|
|
1549
3230
|
}
|
|
3231
|
+
return event.code;
|
|
1550
3232
|
}
|
|
1551
|
-
function
|
|
1552
|
-
|
|
1553
|
-
if (
|
|
1554
|
-
|
|
1555
|
-
return
|
|
1556
|
-
|
|
1557
|
-
|
|
3233
|
+
function errorFromEvent(event) {
|
|
3234
|
+
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
3235
|
+
if (cause instanceof CapxulError) {
|
|
3236
|
+
if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
|
|
3237
|
+
return new CapxulError({
|
|
3238
|
+
code: cause.code,
|
|
3239
|
+
message: redactEmail(cause.message),
|
|
3240
|
+
cause,
|
|
3241
|
+
details: cause.details,
|
|
3242
|
+
operationId: cause.operationId,
|
|
3243
|
+
correlationId: cause.correlationId,
|
|
3244
|
+
retryable: cause.retryable
|
|
3245
|
+
});
|
|
1558
3246
|
}
|
|
3247
|
+
if (cause instanceof CapxulError2) {
|
|
3248
|
+
if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
|
|
3249
|
+
return new CapxulError2(cause.code, redactEmail(cause.message), {
|
|
3250
|
+
cause,
|
|
3251
|
+
details: cause.details,
|
|
3252
|
+
correlationId: cause.correlationId,
|
|
3253
|
+
layer: cause.layer
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
return Errors.providerError("auth", "flow", redactCauseEmail(cause));
|
|
1559
3257
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
3258
|
+
var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
|
|
3259
|
+
var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
3260
|
+
function redactEmail(message) {
|
|
3261
|
+
return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
|
|
1563
3262
|
}
|
|
1564
|
-
function
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
3263
|
+
function redactCauseEmail(cause) {
|
|
3264
|
+
if (cause instanceof Error) {
|
|
3265
|
+
if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
|
|
3266
|
+
const redacted = new Error(redactEmail(cause.message));
|
|
3267
|
+
redacted.cause = cause;
|
|
3268
|
+
return redacted;
|
|
3269
|
+
}
|
|
3270
|
+
if (typeof cause === "string") {
|
|
3271
|
+
return redactEmail(cause);
|
|
1571
3272
|
}
|
|
3273
|
+
return cause;
|
|
1572
3274
|
}
|
|
1573
|
-
function
|
|
1574
|
-
|
|
3275
|
+
function timeoutError(state) {
|
|
3276
|
+
return Errors.providerError(
|
|
3277
|
+
"auth",
|
|
3278
|
+
"flow",
|
|
3279
|
+
new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
|
|
3280
|
+
);
|
|
1575
3281
|
}
|
|
1576
|
-
function
|
|
3282
|
+
function emailDomain(email) {
|
|
3283
|
+
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
3284
|
+
return domain || "unknown";
|
|
3285
|
+
}
|
|
3286
|
+
var initialContext = {
|
|
3287
|
+
email: null,
|
|
3288
|
+
code: null,
|
|
3289
|
+
username: null,
|
|
3290
|
+
signerProvider: null,
|
|
3291
|
+
bootstrapToken: null,
|
|
3292
|
+
bootstrapReason: null,
|
|
3293
|
+
session: null,
|
|
3294
|
+
account: null,
|
|
3295
|
+
safe: null,
|
|
3296
|
+
error: null
|
|
3297
|
+
};
|
|
3298
|
+
function createAuthBootstrapFlowMachine(client) {
|
|
1577
3299
|
return xstate.setup({
|
|
1578
3300
|
types: {},
|
|
1579
3301
|
actors: {
|
|
1580
|
-
// XState v5's `fromPromise` injects an `AbortSignal` that aborts
|
|
1581
|
-
// when the actor is stopped (parent transition fires, machine is
|
|
1582
|
-
// disposed, etc.). Plumbing it through `client.auth.sendOtp` /
|
|
1583
|
-
// `client.auth.verifyOtp` makes the in-flight HTTP request
|
|
1584
|
-
// cancellable: stale responses can't race a state machine
|
|
1585
|
-
// that's already moved on. See PR #406 S5.
|
|
1586
3302
|
sendOtp: xstate.fromPromise(async ({ input, signal }) => {
|
|
1587
3303
|
const [error] = await client.auth.sendOtp(
|
|
1588
3304
|
{ email: input.email },
|
|
@@ -1592,17 +3308,19 @@ function createAuthFlowMachine(client) {
|
|
|
1592
3308
|
}),
|
|
1593
3309
|
verifyOtp: xstate.fromPromise(
|
|
1594
3310
|
async ({ input, signal }) => {
|
|
1595
|
-
const [error,
|
|
1596
|
-
{
|
|
1597
|
-
email: input.email,
|
|
1598
|
-
otp: input.code
|
|
1599
|
-
},
|
|
3311
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
3312
|
+
{ email: input.email, otp: input.code },
|
|
1600
3313
|
{ signal }
|
|
1601
3314
|
);
|
|
1602
3315
|
if (error) throw error;
|
|
1603
|
-
return
|
|
3316
|
+
return result;
|
|
1604
3317
|
}
|
|
1605
3318
|
),
|
|
3319
|
+
completeBootstrap: xstate.fromPromise(async ({ input }) => {
|
|
3320
|
+
const [error, result] = await client.auth.completeBootstrap(input);
|
|
3321
|
+
if (error) throw error;
|
|
3322
|
+
return result;
|
|
3323
|
+
}),
|
|
1606
3324
|
signOut: xstate.fromPromise(async () => {
|
|
1607
3325
|
const [error] = await client.auth.signOut();
|
|
1608
3326
|
if (error) throw error;
|
|
@@ -1612,14 +3330,13 @@ function createAuthFlowMachine(client) {
|
|
|
1612
3330
|
trackOtpRequested: ({ context }) => {
|
|
1613
3331
|
if (!context.email) return;
|
|
1614
3332
|
track("auth_otp_requested", {
|
|
1615
|
-
email_domain:
|
|
3333
|
+
email_domain: emailDomain2(context.email)
|
|
1616
3334
|
});
|
|
1617
3335
|
},
|
|
1618
|
-
|
|
1619
|
-
const error = errorFromEvent(event);
|
|
3336
|
+
trackFailed: ({ event }) => {
|
|
1620
3337
|
track("auth_failed", {
|
|
1621
3338
|
auth_type: "email_otp",
|
|
1622
|
-
reason:
|
|
3339
|
+
reason: errorFromEvent2(event).code
|
|
1623
3340
|
});
|
|
1624
3341
|
},
|
|
1625
3342
|
trackTimeoutFailed: () => {
|
|
@@ -1631,13 +3348,19 @@ function createAuthFlowMachine(client) {
|
|
|
1631
3348
|
trackVerified: () => {
|
|
1632
3349
|
track("auth_verified", { auth_type: "email_otp" });
|
|
1633
3350
|
},
|
|
3351
|
+
trackBootstrapRequired: ({ context }) => {
|
|
3352
|
+
track("auth_verified", {
|
|
3353
|
+
auth_type: "email_otp",
|
|
3354
|
+
auth_mode: context.bootstrapReason ?? "bootstrap_required"
|
|
3355
|
+
});
|
|
3356
|
+
},
|
|
1634
3357
|
identifyAndTrack: ({ context }) => {
|
|
1635
3358
|
if (!context.session) return;
|
|
1636
3359
|
identify(context.session.authUserId, {
|
|
1637
|
-
email_domain:
|
|
3360
|
+
email_domain: emailDomain2(context.session.email)
|
|
1638
3361
|
});
|
|
1639
3362
|
track("auth_identified", {
|
|
1640
|
-
email_domain:
|
|
3363
|
+
email_domain: emailDomain2(context.session.email)
|
|
1641
3364
|
});
|
|
1642
3365
|
},
|
|
1643
3366
|
trackSignedOut: () => {
|
|
@@ -1645,97 +3368,172 @@ function createAuthFlowMachine(client) {
|
|
|
1645
3368
|
}
|
|
1646
3369
|
}
|
|
1647
3370
|
}).createMachine({
|
|
1648
|
-
id: "
|
|
1649
|
-
initial: "
|
|
1650
|
-
context:
|
|
3371
|
+
id: "authBootstrap",
|
|
3372
|
+
initial: "email",
|
|
3373
|
+
context: initialContext,
|
|
1651
3374
|
states: {
|
|
1652
|
-
|
|
3375
|
+
email: {
|
|
1653
3376
|
on: {
|
|
1654
|
-
|
|
1655
|
-
target: "sending_otp",
|
|
3377
|
+
ENTER_EMAIL: {
|
|
1656
3378
|
actions: xstate.assign({
|
|
1657
3379
|
email: ({ event }) => event.email,
|
|
1658
3380
|
error: () => null
|
|
1659
3381
|
})
|
|
1660
|
-
}
|
|
3382
|
+
},
|
|
3383
|
+
REQUEST_OTP: { target: "sending_otp" }
|
|
1661
3384
|
}
|
|
1662
3385
|
},
|
|
1663
3386
|
sending_otp: {
|
|
1664
3387
|
invoke: {
|
|
1665
3388
|
src: "sendOtp",
|
|
1666
|
-
input: ({ context }) => ({ email:
|
|
1667
|
-
onDone: {
|
|
3389
|
+
input: ({ context }) => ({ email: requireEmail2(context) }),
|
|
3390
|
+
onDone: { target: "otp_requested", actions: "trackOtpRequested" },
|
|
3391
|
+
onError: {
|
|
1668
3392
|
target: "otp_requested",
|
|
1669
|
-
actions: [
|
|
3393
|
+
actions: [
|
|
3394
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3395
|
+
"trackFailed"
|
|
3396
|
+
]
|
|
3397
|
+
}
|
|
3398
|
+
},
|
|
3399
|
+
after: {
|
|
3400
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3401
|
+
target: "otp_requested",
|
|
3402
|
+
actions: [
|
|
3403
|
+
xstate.assign({ error: () => timeoutError2("sending_otp") }),
|
|
3404
|
+
"trackTimeoutFailed"
|
|
3405
|
+
]
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
},
|
|
3409
|
+
otp_requested: {
|
|
3410
|
+
on: {
|
|
3411
|
+
ENTER_OTP: {
|
|
3412
|
+
actions: xstate.assign({
|
|
3413
|
+
code: ({ event }) => event.code,
|
|
3414
|
+
error: () => null
|
|
3415
|
+
})
|
|
1670
3416
|
},
|
|
3417
|
+
VERIFY_OTP: { target: "verifying_otp" },
|
|
3418
|
+
BACK: { target: "email" },
|
|
3419
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
3420
|
+
}
|
|
3421
|
+
},
|
|
3422
|
+
verifying_otp: {
|
|
3423
|
+
invoke: {
|
|
3424
|
+
src: "verifyOtp",
|
|
3425
|
+
input: ({ context }) => ({
|
|
3426
|
+
email: requireEmail2(context),
|
|
3427
|
+
code: requireCode(context)
|
|
3428
|
+
}),
|
|
3429
|
+
onDone: [
|
|
3430
|
+
{
|
|
3431
|
+
guard: ({ event }) => event.output.kind === "existing_member",
|
|
3432
|
+
target: "authenticated",
|
|
3433
|
+
actions: [
|
|
3434
|
+
xstate.assign({
|
|
3435
|
+
session: ({ event }) => event.output.session,
|
|
3436
|
+
account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
|
|
3437
|
+
username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
|
|
3438
|
+
safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
|
|
3439
|
+
email: () => null,
|
|
3440
|
+
error: () => null
|
|
3441
|
+
}),
|
|
3442
|
+
"trackVerified",
|
|
3443
|
+
"identifyAndTrack"
|
|
3444
|
+
]
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
target: "bootstrap_required",
|
|
3448
|
+
actions: [
|
|
3449
|
+
xstate.assign({
|
|
3450
|
+
session: ({ event }) => event.output.session,
|
|
3451
|
+
bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
|
|
3452
|
+
bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
|
|
3453
|
+
username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
|
|
3454
|
+
email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
|
|
3455
|
+
error: () => null
|
|
3456
|
+
}),
|
|
3457
|
+
"trackVerified",
|
|
3458
|
+
"trackBootstrapRequired"
|
|
3459
|
+
]
|
|
3460
|
+
}
|
|
3461
|
+
],
|
|
1671
3462
|
onError: {
|
|
1672
|
-
target: "
|
|
3463
|
+
target: "otp_requested",
|
|
1673
3464
|
actions: [
|
|
1674
|
-
xstate.assign({ error: ({ event }) =>
|
|
1675
|
-
"
|
|
3465
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3466
|
+
"trackFailed"
|
|
1676
3467
|
]
|
|
1677
3468
|
}
|
|
1678
3469
|
},
|
|
1679
3470
|
after: {
|
|
1680
3471
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
1681
|
-
target: "
|
|
3472
|
+
target: "otp_requested",
|
|
1682
3473
|
actions: [
|
|
1683
|
-
xstate.assign({
|
|
1684
|
-
error: () => timeoutError("sending_otp")
|
|
1685
|
-
}),
|
|
3474
|
+
xstate.assign({ error: () => timeoutError2("verifying_otp") }),
|
|
1686
3475
|
"trackTimeoutFailed"
|
|
1687
3476
|
]
|
|
1688
3477
|
}
|
|
1689
3478
|
}
|
|
1690
3479
|
},
|
|
1691
|
-
|
|
3480
|
+
bootstrap_required: {
|
|
1692
3481
|
on: {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
3482
|
+
ENTER_USERNAME: {
|
|
3483
|
+
actions: xstate.assign({
|
|
3484
|
+
username: ({ event }) => event.username,
|
|
3485
|
+
error: () => null
|
|
3486
|
+
})
|
|
3487
|
+
},
|
|
3488
|
+
ENTER_SIGNER_PROVIDER: {
|
|
3489
|
+
actions: xstate.assign({
|
|
3490
|
+
signerProvider: ({ event }) => event.signerProvider,
|
|
3491
|
+
error: () => null
|
|
3492
|
+
})
|
|
3493
|
+
},
|
|
3494
|
+
COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
|
|
3495
|
+
BACK: { target: "otp_requested" },
|
|
3496
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
1698
3497
|
}
|
|
1699
3498
|
},
|
|
1700
|
-
|
|
3499
|
+
completing_bootstrap: {
|
|
1701
3500
|
invoke: {
|
|
1702
|
-
src: "
|
|
1703
|
-
input: ({ context
|
|
1704
|
-
|
|
1705
|
-
|
|
3501
|
+
src: "completeBootstrap",
|
|
3502
|
+
input: ({ context }) => ({
|
|
3503
|
+
bootstrapToken: requireBootstrapToken(context),
|
|
3504
|
+
username: requireUsername(context),
|
|
3505
|
+
signerProvider: requireSignerProvider(context)
|
|
1706
3506
|
}),
|
|
1707
3507
|
onDone: {
|
|
1708
3508
|
target: "authenticated",
|
|
1709
3509
|
actions: [
|
|
1710
|
-
// Scrub the duplicate `context.email` (input value
|
|
1711
|
-
// captured during sendOtp) since the verified
|
|
1712
|
-
// `session.email` is now the canonical source
|
|
1713
|
-
// post-authentication. The session's email is
|
|
1714
|
-
// intentionally retained — it's the auth result, not
|
|
1715
|
-
// lingering input. See PR #406 S2.
|
|
1716
3510
|
xstate.assign({
|
|
1717
|
-
session: ({ event }) => event.output,
|
|
1718
|
-
|
|
3511
|
+
session: ({ event }) => event.output.session,
|
|
3512
|
+
account: ({ event }) => event.output.account,
|
|
3513
|
+
username: ({ event }) => event.output.username,
|
|
3514
|
+
safe: ({ event }) => event.output.safe,
|
|
3515
|
+
bootstrapToken: () => null,
|
|
3516
|
+
bootstrapReason: () => null,
|
|
3517
|
+
signerProvider: () => null,
|
|
3518
|
+
email: () => null,
|
|
3519
|
+
error: () => null
|
|
1719
3520
|
}),
|
|
1720
|
-
"trackVerified",
|
|
1721
3521
|
"identifyAndTrack"
|
|
1722
3522
|
]
|
|
1723
3523
|
},
|
|
1724
3524
|
onError: {
|
|
1725
|
-
target: "
|
|
3525
|
+
target: "bootstrap_required",
|
|
1726
3526
|
actions: [
|
|
1727
|
-
xstate.assign({ error: ({ event }) =>
|
|
1728
|
-
"
|
|
3527
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
3528
|
+
"trackFailed"
|
|
1729
3529
|
]
|
|
1730
3530
|
}
|
|
1731
3531
|
},
|
|
1732
3532
|
after: {
|
|
1733
3533
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
1734
|
-
target: "
|
|
3534
|
+
target: "bootstrap_required",
|
|
1735
3535
|
actions: [
|
|
1736
|
-
xstate.assign({
|
|
1737
|
-
error: () => timeoutError("verifying")
|
|
1738
|
-
}),
|
|
3536
|
+
xstate.assign({ error: () => timeoutError2("completing_bootstrap") }),
|
|
1739
3537
|
"trackTimeoutFailed"
|
|
1740
3538
|
]
|
|
1741
3539
|
}
|
|
@@ -1750,220 +3548,80 @@ function createAuthFlowMachine(client) {
|
|
|
1750
3548
|
invoke: {
|
|
1751
3549
|
src: "signOut",
|
|
1752
3550
|
onDone: {
|
|
1753
|
-
target: "
|
|
3551
|
+
target: "email",
|
|
1754
3552
|
actions: [
|
|
1755
|
-
xstate.assign(
|
|
1756
|
-
session: () => null,
|
|
1757
|
-
email: () => null,
|
|
1758
|
-
error: () => null
|
|
1759
|
-
}),
|
|
3553
|
+
xstate.assign(() => initialContext),
|
|
1760
3554
|
"trackSignedOut"
|
|
1761
3555
|
]
|
|
1762
3556
|
},
|
|
1763
3557
|
onError: {
|
|
1764
3558
|
target: "error",
|
|
1765
|
-
actions: xstate.assign({ error: ({ event }) =>
|
|
3559
|
+
actions: xstate.assign({ error: ({ event }) => errorFromEvent2(event) })
|
|
1766
3560
|
}
|
|
1767
3561
|
}
|
|
1768
3562
|
},
|
|
1769
3563
|
error: {
|
|
1770
3564
|
on: {
|
|
1771
|
-
RESET: {
|
|
1772
|
-
target: "idle",
|
|
1773
|
-
actions: xstate.assign({ error: () => null })
|
|
1774
|
-
}
|
|
3565
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
1775
3566
|
}
|
|
1776
3567
|
}
|
|
1777
3568
|
}
|
|
1778
3569
|
});
|
|
1779
3570
|
}
|
|
1780
|
-
function
|
|
3571
|
+
function requireEmail2(context) {
|
|
1781
3572
|
if (!context.email) {
|
|
1782
|
-
throw Errors.invalidInput(
|
|
1783
|
-
"email",
|
|
1784
|
-
"Auth flow advanced without an email captured in context."
|
|
1785
|
-
);
|
|
3573
|
+
throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
|
|
1786
3574
|
}
|
|
1787
3575
|
return context.email;
|
|
1788
3576
|
}
|
|
1789
|
-
function
|
|
1790
|
-
if (
|
|
3577
|
+
function requireCode(context) {
|
|
3578
|
+
if (!context.code) {
|
|
3579
|
+
throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
|
|
3580
|
+
}
|
|
3581
|
+
return context.code;
|
|
3582
|
+
}
|
|
3583
|
+
function requireBootstrapToken(context) {
|
|
3584
|
+
if (!context.bootstrapToken) {
|
|
1791
3585
|
throw Errors.invalidInput(
|
|
1792
|
-
"
|
|
1793
|
-
|
|
3586
|
+
"bootstrapToken",
|
|
3587
|
+
"Auth bootstrap requires a continuation token."
|
|
1794
3588
|
);
|
|
1795
3589
|
}
|
|
1796
|
-
return
|
|
3590
|
+
return context.bootstrapToken;
|
|
1797
3591
|
}
|
|
1798
|
-
function
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
|
|
1802
|
-
return new CapxulError({
|
|
1803
|
-
code: cause.code,
|
|
1804
|
-
message: redactEmail(cause.message),
|
|
1805
|
-
cause,
|
|
1806
|
-
details: cause.details,
|
|
1807
|
-
operationId: cause.operationId,
|
|
1808
|
-
correlationId: cause.correlationId,
|
|
1809
|
-
retryable: cause.retryable
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
if (cause instanceof CapxulError2) {
|
|
1813
|
-
if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
|
|
1814
|
-
return new CapxulError2(cause.code, redactEmail(cause.message), {
|
|
1815
|
-
cause,
|
|
1816
|
-
details: cause.details,
|
|
1817
|
-
correlationId: cause.correlationId,
|
|
1818
|
-
layer: cause.layer
|
|
1819
|
-
});
|
|
3592
|
+
function requireUsername(context) {
|
|
3593
|
+
if (!context.username) {
|
|
3594
|
+
throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
|
|
1820
3595
|
}
|
|
1821
|
-
return
|
|
1822
|
-
}
|
|
1823
|
-
var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
|
|
1824
|
-
var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
1825
|
-
function redactEmail(message) {
|
|
1826
|
-
return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
|
|
3596
|
+
return context.username;
|
|
1827
3597
|
}
|
|
1828
|
-
function
|
|
1829
|
-
if (
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
3598
|
+
function requireSignerProvider(context) {
|
|
3599
|
+
if (!context.signerProvider) {
|
|
3600
|
+
throw Errors.invalidInput(
|
|
3601
|
+
"signerProvider",
|
|
3602
|
+
"Auth bootstrap requires a signer provider."
|
|
3603
|
+
);
|
|
1834
3604
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
3605
|
+
return context.signerProvider;
|
|
3606
|
+
}
|
|
3607
|
+
function errorFromEvent2(event) {
|
|
3608
|
+
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
3609
|
+
if (cause instanceof CapxulError || cause instanceof CapxulError2) {
|
|
3610
|
+
return cause;
|
|
1837
3611
|
}
|
|
1838
|
-
return cause;
|
|
3612
|
+
return Errors.providerError("auth", "bootstrap", cause);
|
|
1839
3613
|
}
|
|
1840
|
-
function
|
|
3614
|
+
function timeoutError2(state) {
|
|
1841
3615
|
return Errors.providerError(
|
|
1842
3616
|
"auth",
|
|
1843
|
-
"
|
|
3617
|
+
"bootstrap",
|
|
1844
3618
|
new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
|
|
1845
3619
|
);
|
|
1846
3620
|
}
|
|
1847
|
-
function
|
|
3621
|
+
function emailDomain2(email) {
|
|
1848
3622
|
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
1849
3623
|
return domain || "unknown";
|
|
1850
3624
|
}
|
|
1851
|
-
|
|
1852
|
-
// ../platform-kernel/src/ids.ts
|
|
1853
|
-
function makePrefixedIdConstructor(prefix, fieldName) {
|
|
1854
|
-
const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
|
|
1855
|
-
return (raw) => {
|
|
1856
|
-
if (typeof raw !== "string" || !re.test(raw)) {
|
|
1857
|
-
throw new Error(
|
|
1858
|
-
`Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
|
|
1859
|
-
);
|
|
1860
|
-
}
|
|
1861
|
-
return raw;
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
var toAccountId = makePrefixedIdConstructor(
|
|
1865
|
-
"acct",
|
|
1866
|
-
"accountId"
|
|
1867
|
-
);
|
|
1868
|
-
var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
|
|
1869
|
-
var toMemberId = makePrefixedIdConstructor(
|
|
1870
|
-
"mem",
|
|
1871
|
-
"memberId"
|
|
1872
|
-
);
|
|
1873
|
-
var toSafeId = makePrefixedIdConstructor(
|
|
1874
|
-
"safe",
|
|
1875
|
-
"safeId"
|
|
1876
|
-
);
|
|
1877
|
-
var toTreasuryId = makePrefixedIdConstructor(
|
|
1878
|
-
"try",
|
|
1879
|
-
"treasuryId"
|
|
1880
|
-
);
|
|
1881
|
-
var toApiKeyId = makePrefixedIdConstructor(
|
|
1882
|
-
"ak",
|
|
1883
|
-
"apiKeyId"
|
|
1884
|
-
);
|
|
1885
|
-
var toOperationId = makePrefixedIdConstructor(
|
|
1886
|
-
"op",
|
|
1887
|
-
"operationId"
|
|
1888
|
-
);
|
|
1889
|
-
var toKycProfileId = makePrefixedIdConstructor(
|
|
1890
|
-
"kyc",
|
|
1891
|
-
"kycProfileId"
|
|
1892
|
-
);
|
|
1893
|
-
var toKybProfileId = makePrefixedIdConstructor(
|
|
1894
|
-
"kyb",
|
|
1895
|
-
"kybProfileId"
|
|
1896
|
-
);
|
|
1897
|
-
var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
|
|
1898
|
-
var toPaymentId = makePrefixedIdConstructor(
|
|
1899
|
-
"pay",
|
|
1900
|
-
"paymentId"
|
|
1901
|
-
);
|
|
1902
|
-
var toWithdrawalId = makePrefixedIdConstructor(
|
|
1903
|
-
"wd",
|
|
1904
|
-
"withdrawalId"
|
|
1905
|
-
);
|
|
1906
|
-
var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
|
|
1907
|
-
var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
|
|
1908
|
-
var toSubAccountId = makePrefixedIdConstructor(
|
|
1909
|
-
"sub",
|
|
1910
|
-
"subAccountId"
|
|
1911
|
-
);
|
|
1912
|
-
var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
|
|
1913
|
-
var toVirtualCardId = makePrefixedIdConstructor(
|
|
1914
|
-
"vc",
|
|
1915
|
-
"virtualCardId"
|
|
1916
|
-
);
|
|
1917
|
-
var toTransferId = makePrefixedIdConstructor(
|
|
1918
|
-
"txfr",
|
|
1919
|
-
"transferId"
|
|
1920
|
-
);
|
|
1921
|
-
var toDocumentId = makePrefixedIdConstructor(
|
|
1922
|
-
"doc",
|
|
1923
|
-
"documentId"
|
|
1924
|
-
);
|
|
1925
|
-
var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
|
|
1926
|
-
|
|
1927
|
-
// ../platform-kernel/src/value-objects.ts
|
|
1928
|
-
var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
|
|
1929
|
-
function toPhoneNumber(raw) {
|
|
1930
|
-
if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
|
|
1931
|
-
throw new Error(
|
|
1932
|
-
`Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
|
|
1933
|
-
);
|
|
1934
|
-
}
|
|
1935
|
-
return raw;
|
|
1936
|
-
}
|
|
1937
|
-
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1938
|
-
function toEmail(raw) {
|
|
1939
|
-
if (typeof raw !== "string") {
|
|
1940
|
-
throw new Error(
|
|
1941
|
-
`Invalid email: expected string, got ${String(raw)}`
|
|
1942
|
-
);
|
|
1943
|
-
}
|
|
1944
|
-
const lowered = raw.trim().toLowerCase();
|
|
1945
|
-
if (!EMAIL_RE.test(lowered)) {
|
|
1946
|
-
throw new Error(
|
|
1947
|
-
`Invalid email: expected local@domain.tld, got ${String(raw)}`
|
|
1948
|
-
);
|
|
1949
|
-
}
|
|
1950
|
-
return lowered;
|
|
1951
|
-
}
|
|
1952
|
-
var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
|
|
1953
|
-
function toUsername(raw) {
|
|
1954
|
-
if (typeof raw !== "string") {
|
|
1955
|
-
throw new Error(
|
|
1956
|
-
`Invalid username: expected string, got ${String(raw)}`
|
|
1957
|
-
);
|
|
1958
|
-
}
|
|
1959
|
-
const lowered = raw.toLowerCase();
|
|
1960
|
-
if (!USERNAME_RE.test(lowered)) {
|
|
1961
|
-
throw new Error(
|
|
1962
|
-
`Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
|
|
1963
|
-
);
|
|
1964
|
-
}
|
|
1965
|
-
return lowered;
|
|
1966
|
-
}
|
|
1967
3625
|
function createProvisioningMachine(client) {
|
|
1968
3626
|
return xstate.setup({
|
|
1969
3627
|
types: {},
|
|
@@ -2036,13 +3694,13 @@ function createProvisioningMachine(client) {
|
|
|
2036
3694
|
},
|
|
2037
3695
|
onError: {
|
|
2038
3696
|
target: "error",
|
|
2039
|
-
actions: xstate.assign({ error: ({ event }) =>
|
|
3697
|
+
actions: xstate.assign({ error: ({ event }) => errorFromEvent3(event) })
|
|
2040
3698
|
}
|
|
2041
3699
|
},
|
|
2042
3700
|
after: {
|
|
2043
3701
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2044
3702
|
target: "error",
|
|
2045
|
-
actions: xstate.assign({ error: () =>
|
|
3703
|
+
actions: xstate.assign({ error: () => timeoutError3() })
|
|
2046
3704
|
}
|
|
2047
3705
|
}
|
|
2048
3706
|
},
|
|
@@ -2060,7 +3718,7 @@ function createProvisioningMachine(client) {
|
|
|
2060
3718
|
* this payload on its `onDone` transition and branches via guards
|
|
2061
3719
|
* on `event.output.error`.
|
|
2062
3720
|
*/
|
|
2063
|
-
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error:
|
|
3721
|
+
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
|
|
2064
3722
|
});
|
|
2065
3723
|
}
|
|
2066
3724
|
function requireProvisionInput(context) {
|
|
@@ -2072,13 +3730,13 @@ function requireProvisionInput(context) {
|
|
|
2072
3730
|
}
|
|
2073
3731
|
return context.input;
|
|
2074
3732
|
}
|
|
2075
|
-
function
|
|
3733
|
+
function errorFromEvent3(event) {
|
|
2076
3734
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2077
3735
|
if (cause instanceof CapxulError) return cause;
|
|
2078
3736
|
if (cause instanceof CapxulError2) return cause;
|
|
2079
3737
|
return Errors.providerError("provisioning", "flow", cause);
|
|
2080
3738
|
}
|
|
2081
|
-
function
|
|
3739
|
+
function timeoutError3() {
|
|
2082
3740
|
return Errors.providerError(
|
|
2083
3741
|
"provisioning",
|
|
2084
3742
|
"flow",
|
|
@@ -2171,7 +3829,7 @@ function createOnboardingFlowMachine(client) {
|
|
|
2171
3829
|
error: ({ event }) => extractChildErrorOrFallback(event)
|
|
2172
3830
|
}),
|
|
2173
3831
|
assignChildThrown: xstate.assign({
|
|
2174
|
-
error: ({ event }) =>
|
|
3832
|
+
error: ({ event }) => errorFromEvent4(event)
|
|
2175
3833
|
}),
|
|
2176
3834
|
assignAccountFromChild: xstate.assign({
|
|
2177
3835
|
account: ({ event }) => extractChildAccountOrNull(event)
|
|
@@ -2333,7 +3991,7 @@ function extractChildAccountOrNull(event) {
|
|
|
2333
3991
|
if (output && "account" in output && output.account) return output.account;
|
|
2334
3992
|
return null;
|
|
2335
3993
|
}
|
|
2336
|
-
function
|
|
3994
|
+
function errorFromEvent4(event) {
|
|
2337
3995
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2338
3996
|
if (cause instanceof CapxulError) return cause;
|
|
2339
3997
|
if (cause instanceof CapxulError2) return cause;
|
|
@@ -2350,12 +4008,13 @@ function createCapxulClient(config = {}) {
|
|
|
2350
4008
|
organizations: createOrganizationsClient(config),
|
|
2351
4009
|
payments: createPaymentsClient(config),
|
|
2352
4010
|
transfers: createTransfersClient(),
|
|
4011
|
+
tokenTransfers: createTokenTransfersClient(config),
|
|
2353
4012
|
withdrawals: createWithdrawalsClient(config),
|
|
2354
4013
|
documents: createDocumentsClient(),
|
|
2355
|
-
subAccounts: createSubAccountsClient(),
|
|
4014
|
+
subAccounts: createSubAccountsClient(config),
|
|
2356
4015
|
virtualAccounts: createVirtualAccountsClient(),
|
|
2357
4016
|
virtualCards: createVirtualCardsClient(),
|
|
2358
|
-
externalAccounts: createExternalAccountsClient(),
|
|
4017
|
+
externalAccounts: createExternalAccountsClient(config),
|
|
2359
4018
|
operations: createOperationsClient(config),
|
|
2360
4019
|
webhookEndpoints: createWebhookEndpointsClient(),
|
|
2361
4020
|
webhookEvents: createWebhookEventsClient(),
|
|
@@ -2364,6 +4023,7 @@ function createCapxulClient(config = {}) {
|
|
|
2364
4023
|
const client = clientWithoutFlows;
|
|
2365
4024
|
client.flows = {
|
|
2366
4025
|
auth: () => createAuthFlowMachine(client),
|
|
4026
|
+
authBootstrap: () => createAuthBootstrapFlowMachine(client),
|
|
2367
4027
|
onboarding: () => createOnboardingFlowMachine(client),
|
|
2368
4028
|
provisioning: () => createProvisioningMachine(client)
|
|
2369
4029
|
};
|
|
@@ -2469,6 +4129,7 @@ function isWebhookEvent(value) {
|
|
|
2469
4129
|
}
|
|
2470
4130
|
|
|
2471
4131
|
exports.CapxulError = CapxulError;
|
|
4132
|
+
exports.createAuthBootstrapFlowMachine = createAuthBootstrapFlowMachine;
|
|
2472
4133
|
exports.createAuthFlowMachine = createAuthFlowMachine;
|
|
2473
4134
|
exports.createCapxulClient = createCapxulClient;
|
|
2474
4135
|
exports.createLocalSigner = createLocalSigner;
|
|
@@ -2493,6 +4154,7 @@ exports.toPaymentId = toPaymentId;
|
|
|
2493
4154
|
exports.toPhoneNumber = toPhoneNumber;
|
|
2494
4155
|
exports.toSafeId = toSafeId;
|
|
2495
4156
|
exports.toSubAccountId = toSubAccountId;
|
|
4157
|
+
exports.toTokenTransferId = toTokenTransferId;
|
|
2496
4158
|
exports.toTransferId = toTransferId;
|
|
2497
4159
|
exports.toTreasuryId = toTreasuryId;
|
|
2498
4160
|
exports.toUsername = toUsername;
|
|
@@ -2501,4 +4163,5 @@ exports.toVirtualCardId = toVirtualCardId;
|
|
|
2501
4163
|
exports.toWebhookEndpointId = toWebhookEndpointId;
|
|
2502
4164
|
exports.toWebhookEventId = toWebhookEventId;
|
|
2503
4165
|
exports.toWithdrawalId = toWithdrawalId;
|
|
4166
|
+
exports.tryCatch = tryCatch;
|
|
2504
4167
|
exports.verifyWebhook = verifyWebhook;
|