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