@capxul/sdk 0.2.0-alpha.4 → 0.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -308
- package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs +113 -0
- package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs.map +1 -0
- package/dist/index.d.mts +1263 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4740 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node/index.d.mts +55 -0
- package/dist/node/index.d.mts.map +1 -0
- package/dist/node/index.mjs +159 -0
- package/dist/node/index.mjs.map +1 -0
- package/dist/ports/safe-deployment.d.mts +2 -0
- package/dist/ports/safe-deployment.mjs +38 -0
- package/dist/ports/safe-deployment.mjs.map +1 -0
- package/dist/safe-deployment-Vni46k3t.d.mts +137 -0
- package/dist/safe-deployment-Vni46k3t.d.mts.map +1 -0
- package/dist/signer-oaYGfjDe.d.mts +142 -0
- package/dist/signer-oaYGfjDe.d.mts.map +1 -0
- package/package.json +37 -70
- package/CHANGELOG.md +0 -274
- package/LICENSE +0 -44
- package/dist/client-CbUeJoM9.d.ts +0 -1335
- package/dist/client-UEm2oZbZ.d.cts +0 -1335
- package/dist/client.cjs +0 -4042
- package/dist/client.d.cts +0 -6
- package/dist/client.d.ts +0 -6
- package/dist/client.js +0 -4040
- package/dist/errors-CwhCWGxm.d.ts +0 -70
- package/dist/errors-rqxuUhQP.d.cts +0 -70
- package/dist/errors.cjs +0 -35
- package/dist/errors.d.cts +0 -2
- package/dist/errors.d.ts +0 -2
- package/dist/errors.js +0 -31
- package/dist/index.cjs +0 -4273
- package/dist/index.d.cts +0 -461
- package/dist/index.d.ts +0 -461
- package/dist/index.js +0 -4234
- package/dist/next-action-CTGl8wpy.d.cts +0 -177
- package/dist/next-action-CTGl8wpy.d.ts +0 -177
- package/dist/types-BD4VAQb5.d.ts +0 -1205
- package/dist/types-HlwCIjgQ.d.cts +0 -1205
- package/dist/webhooks.cjs +0 -118
- package/dist/webhooks.d.cts +0 -33
- package/dist/webhooks.d.ts +0 -33
- package/dist/webhooks.js +0 -116
package/dist/client.cjs
DELETED
|
@@ -1,4042 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var server = require('convex/server');
|
|
4
|
-
var accounts = require('permissionless/accounts');
|
|
5
|
-
var viem = require('viem');
|
|
6
|
-
var accountAbstraction = require('viem/account-abstraction');
|
|
7
|
-
var chains = require('viem/chains');
|
|
8
|
-
var browser = require('convex/browser');
|
|
9
|
-
var xstate = require('xstate');
|
|
10
|
-
|
|
11
|
-
// src/_generated/api.js
|
|
12
|
-
var api = server.anyApi;
|
|
13
|
-
server.componentsGeneric();
|
|
14
|
-
|
|
15
|
-
// src/errors.ts
|
|
16
|
-
var CapxulError = class extends Error {
|
|
17
|
-
code;
|
|
18
|
-
details;
|
|
19
|
-
operationId;
|
|
20
|
-
correlationId;
|
|
21
|
-
retryable;
|
|
22
|
-
constructor(init) {
|
|
23
|
-
super(
|
|
24
|
-
init.message,
|
|
25
|
-
init.cause !== void 0 ? { cause: init.cause } : void 0
|
|
26
|
-
);
|
|
27
|
-
this.name = "CapxulError";
|
|
28
|
-
this.code = init.code;
|
|
29
|
-
this.details = init.details;
|
|
30
|
-
this.operationId = init.operationId;
|
|
31
|
-
this.correlationId = init.correlationId;
|
|
32
|
-
this.retryable = init.retryable;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
function notImplemented(method) {
|
|
36
|
-
return new CapxulError({
|
|
37
|
-
code: "NOT_IMPLEMENTED",
|
|
38
|
-
message: `${method} is not yet implemented in @capxul/sdk (Slice C scaffold).`
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function stub(method) {
|
|
42
|
-
return [notImplemented(method), null];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/internal/convex-error.ts
|
|
46
|
-
function isConvexClientError(error) {
|
|
47
|
-
if (typeof error !== "object" || error === null || !("data" in error)) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
const data = error.data;
|
|
51
|
-
return typeof data === "object" && data !== null;
|
|
52
|
-
}
|
|
53
|
-
function fromConvexError(error) {
|
|
54
|
-
if (error instanceof CapxulError) {
|
|
55
|
-
return error;
|
|
56
|
-
}
|
|
57
|
-
if (isConvexClientError(error)) {
|
|
58
|
-
return new CapxulError({
|
|
59
|
-
code: error.data.code ?? "UNKNOWN",
|
|
60
|
-
message: error.data.message ?? error.message,
|
|
61
|
-
details: error.data.details,
|
|
62
|
-
correlationId: error.data.correlationId,
|
|
63
|
-
cause: error
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return new CapxulError({
|
|
67
|
-
code: "UNKNOWN",
|
|
68
|
-
message: error instanceof Error ? error.message : String(error),
|
|
69
|
-
cause: error
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ../observability/src/try-catch.ts
|
|
74
|
-
async function tryCatch(promise) {
|
|
75
|
-
try {
|
|
76
|
-
return [null, await promise];
|
|
77
|
-
} catch (e) {
|
|
78
|
-
return [e instanceof Error ? e : new Error(String(e)), null];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ../observability/src/debug-log.ts
|
|
83
|
-
function isDevelopmentBuild() {
|
|
84
|
-
if (typeof process === "undefined") {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
|
|
88
|
-
}
|
|
89
|
-
function debugLog(line) {
|
|
90
|
-
if (!isDevelopmentBuild()) return;
|
|
91
|
-
if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
|
|
92
|
-
console.info(line);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
|
|
96
|
-
process.stderr.write(`${line}
|
|
97
|
-
`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function formatDebugValue(value) {
|
|
101
|
-
if (value === void 0 || value === "") return "";
|
|
102
|
-
if (typeof value === "string") return value;
|
|
103
|
-
try {
|
|
104
|
-
return JSON.stringify(value);
|
|
105
|
-
} catch {
|
|
106
|
-
return String(value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function track(...args) {
|
|
110
|
-
const [name, props] = args;
|
|
111
|
-
debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ../config/src/chain.ts
|
|
115
|
-
var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
|
|
116
|
-
var TEST_USDC_ADDRESS = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
|
|
117
|
-
var CAPXUL_API_BASE_URL = "https://elated-oyster-269.convex.site";
|
|
118
|
-
|
|
119
|
-
// ../config/src/timing.ts
|
|
120
|
-
var FLOW_INVOKE_TIMEOUT_MS = 3e4;
|
|
121
|
-
|
|
122
|
-
// ../config/src/errors.ts
|
|
123
|
-
var CapxulError2 = class extends Error {
|
|
124
|
-
code;
|
|
125
|
-
details;
|
|
126
|
-
correlationId;
|
|
127
|
-
layer;
|
|
128
|
-
constructor(code, message, options) {
|
|
129
|
-
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
130
|
-
this.code = code;
|
|
131
|
-
this.details = options?.details;
|
|
132
|
-
this.correlationId = options?.correlationId;
|
|
133
|
-
this.layer = options?.layer;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
var Errors = {
|
|
137
|
-
notAuthenticated: () => new CapxulError2("NOT_AUTHENTICATED", "Not authenticated"),
|
|
138
|
-
profileNotFound: (authUserId) => new CapxulError2("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`),
|
|
139
|
-
smartAccountMissing: (authUserId) => new CapxulError2("SMART_ACCOUNT_MISSING", `Smart account not provisioned for user ${authUserId}`),
|
|
140
|
-
envMissing: (name) => new CapxulError2("ENV_MISSING", `Environment variable ${name} not configured`),
|
|
141
|
-
openfortApi: (operation, cause) => new CapxulError2(
|
|
142
|
-
"PROVIDER_ERROR",
|
|
143
|
-
`Openfort ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
144
|
-
{ cause, details: { provider: "openfort", operation } }
|
|
145
|
-
),
|
|
146
|
-
shieldApi: (status, detail) => new CapxulError2(
|
|
147
|
-
"PROVIDER_ERROR",
|
|
148
|
-
`Shield API error (${status}): ${detail}`,
|
|
149
|
-
{ details: { provider: "shield", status } }
|
|
150
|
-
),
|
|
151
|
-
providerError: (provider, operation, cause) => (
|
|
152
|
-
// Public `message` is redacted to a fixed shape so provider-side
|
|
153
|
-
// exception text never leaks to the client. The original `cause`
|
|
154
|
-
// is preserved on `Error.cause` for server-side debugging via
|
|
155
|
-
// observability sinks (Sentry, console traces).
|
|
156
|
-
new CapxulError2(
|
|
157
|
-
"PROVIDER_ERROR",
|
|
158
|
-
`Provider error: ${provider} ${operation}`,
|
|
159
|
-
{ cause, details: { provider, operation } }
|
|
160
|
-
)
|
|
161
|
-
),
|
|
162
|
-
invalidInput: (field, reason) => new CapxulError2(
|
|
163
|
-
"INVALID_INPUT",
|
|
164
|
-
`Invalid ${field}: ${reason}`,
|
|
165
|
-
{ details: { field, reason } }
|
|
166
|
-
),
|
|
167
|
-
playerNotFound: (playerId) => new CapxulError2(
|
|
168
|
-
"PLAYER_NOT_FOUND",
|
|
169
|
-
playerId ? `Openfort player ${playerId} not found` : "Openfort player not found"
|
|
170
|
-
),
|
|
171
|
-
accountNotFound: (accountId) => new CapxulError2(
|
|
172
|
-
"ACCOUNT_NOT_FOUND",
|
|
173
|
-
accountId ? `Openfort account ${accountId} not found` : "Openfort smart account not found"
|
|
174
|
-
),
|
|
175
|
-
invalidRecipient: (reason) => new CapxulError2("INVALID_RECIPIENT", reason),
|
|
176
|
-
permissionDenied: (reason = "Permission denied") => new CapxulError2("PERMISSION_DENIED", reason),
|
|
177
|
-
notFound: (resource, id) => new CapxulError2(
|
|
178
|
-
"NOT_FOUND",
|
|
179
|
-
id ? `${resource} ${id} not found` : `${resource} not found`
|
|
180
|
-
),
|
|
181
|
-
idempotencyConflict: (details) => new CapxulError2(
|
|
182
|
-
"IDEMPOTENCY_CONFLICT",
|
|
183
|
-
"Idempotency key was already used for a different request",
|
|
184
|
-
{ details }
|
|
185
|
-
),
|
|
186
|
-
emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
|
|
187
|
-
details
|
|
188
|
-
}),
|
|
189
|
-
rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
|
|
190
|
-
details: { ...details }
|
|
191
|
-
}),
|
|
192
|
-
internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
193
|
-
/**
|
|
194
|
-
* Verification gate. Surfaced when a request hits a verification
|
|
195
|
-
* boundary the actor cannot cross under their current state. Two
|
|
196
|
-
* variants share this code:
|
|
197
|
-
*
|
|
198
|
-
* - Rail gate (Withdrawals v1 W2, #465): the resolved
|
|
199
|
-
* `external_account.kind` routes to a withdrawal rail (e.g.
|
|
200
|
-
* `fiat_offramp`, `card_payout`) that is not yet supported. Carries
|
|
201
|
-
* `details.rail` + `details.currentKind`.
|
|
202
|
-
* - KYC tier gate (legacy / future): the actor's KYC tier is below
|
|
203
|
-
* the required tier. Carries `details.requiredTier`.
|
|
204
|
-
*
|
|
205
|
-
* Code is shared because both expose the same UX shape ("you cannot
|
|
206
|
-
* proceed until verification advances"); the `details.*` keys
|
|
207
|
-
* differentiate the route.
|
|
208
|
-
*/
|
|
209
|
-
verificationRequired: (details) => {
|
|
210
|
-
const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
|
|
211
|
-
return new CapxulError2("VERIFICATION_REQUIRED", message, {
|
|
212
|
-
details: { ...details }
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// ../config/src/safe.ts
|
|
218
|
-
var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
|
|
219
|
-
var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
|
|
220
|
-
var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
|
|
221
|
-
var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
|
|
222
|
-
var MULTI_SEND = "0x38869bf66a61cf6bdb996a6ae40d5853fd43b526";
|
|
223
|
-
|
|
224
|
-
// ../config/src/org-roles.ts
|
|
225
|
-
function roleKeyFromLabel(label) {
|
|
226
|
-
const bytes = new TextEncoder().encode(label);
|
|
227
|
-
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
228
|
-
return "0x" + hex.padEnd(64, "0");
|
|
229
|
-
}
|
|
230
|
-
roleKeyFromLabel("OWNER");
|
|
231
|
-
roleKeyFromLabel("FINANCE_MANAGER");
|
|
232
|
-
roleKeyFromLabel("PAYMENTS_OPERATOR");
|
|
233
|
-
var defaultSafeDeriveConfig = {
|
|
234
|
-
safeProxyFactory: SAFE_PROXY_FACTORY,
|
|
235
|
-
safeL2Singleton: SAFE_L2_SINGLETON,
|
|
236
|
-
safeModuleSetup: SAFE_MODULE_SETUP,
|
|
237
|
-
safe4337Module: SAFE_4337_MODULE,
|
|
238
|
-
multiSend: MULTI_SEND
|
|
239
|
-
};
|
|
240
|
-
var SAFE_PROXY_CREATION_CODE = "0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564";
|
|
241
|
-
var enableModulesAbi = [
|
|
242
|
-
{
|
|
243
|
-
type: "function",
|
|
244
|
-
name: "enableModules",
|
|
245
|
-
inputs: [{ type: "address[]", name: "modules" }],
|
|
246
|
-
outputs: [],
|
|
247
|
-
stateMutability: "nonpayable"
|
|
248
|
-
}
|
|
249
|
-
];
|
|
250
|
-
var multiSendAbi = [
|
|
251
|
-
{
|
|
252
|
-
type: "function",
|
|
253
|
-
name: "multiSend",
|
|
254
|
-
inputs: [{ type: "bytes", name: "transactions" }],
|
|
255
|
-
outputs: [],
|
|
256
|
-
stateMutability: "payable"
|
|
257
|
-
}
|
|
258
|
-
];
|
|
259
|
-
var setupAbi = [
|
|
260
|
-
{
|
|
261
|
-
type: "function",
|
|
262
|
-
name: "setup",
|
|
263
|
-
inputs: [
|
|
264
|
-
{ type: "address[]", name: "owners" },
|
|
265
|
-
{ type: "uint256", name: "threshold" },
|
|
266
|
-
{ type: "address", name: "to" },
|
|
267
|
-
{ type: "bytes", name: "data" },
|
|
268
|
-
{ type: "address", name: "fallbackHandler" },
|
|
269
|
-
{ type: "address", name: "paymentToken" },
|
|
270
|
-
{ type: "uint256", name: "payment" },
|
|
271
|
-
{ type: "address", name: "paymentReceiver" }
|
|
272
|
-
],
|
|
273
|
-
outputs: [],
|
|
274
|
-
stateMutability: "nonpayable"
|
|
275
|
-
}
|
|
276
|
-
];
|
|
277
|
-
function encodeInternalTransaction(tx) {
|
|
278
|
-
const encoded = viem.encodePacked(
|
|
279
|
-
["uint8", "address", "uint256", "uint256", "bytes"],
|
|
280
|
-
[
|
|
281
|
-
tx.operation,
|
|
282
|
-
tx.to,
|
|
283
|
-
tx.value,
|
|
284
|
-
BigInt(tx.data.slice(2).length / 2),
|
|
285
|
-
tx.data
|
|
286
|
-
]
|
|
287
|
-
);
|
|
288
|
-
return encoded.slice(2);
|
|
289
|
-
}
|
|
290
|
-
function computeSaltNonce(ownerAddress) {
|
|
291
|
-
return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
|
|
292
|
-
}
|
|
293
|
-
function deriveSafeAddress(signerAddress, config = defaultSafeDeriveConfig) {
|
|
294
|
-
const saltNonce = computeSaltNonce(signerAddress);
|
|
295
|
-
const enableModulesData = viem.encodeFunctionData({
|
|
296
|
-
abi: enableModulesAbi,
|
|
297
|
-
functionName: "enableModules",
|
|
298
|
-
args: [[config.safe4337Module]]
|
|
299
|
-
});
|
|
300
|
-
const innerTx = encodeInternalTransaction({
|
|
301
|
-
operation: 1,
|
|
302
|
-
to: config.safeModuleSetup,
|
|
303
|
-
value: 0n,
|
|
304
|
-
data: enableModulesData
|
|
305
|
-
});
|
|
306
|
-
const multiSendCallData = viem.encodeFunctionData({
|
|
307
|
-
abi: multiSendAbi,
|
|
308
|
-
functionName: "multiSend",
|
|
309
|
-
args: [`0x${innerTx}`]
|
|
310
|
-
});
|
|
311
|
-
const initializer = viem.encodeFunctionData({
|
|
312
|
-
abi: setupAbi,
|
|
313
|
-
functionName: "setup",
|
|
314
|
-
args: [
|
|
315
|
-
[signerAddress],
|
|
316
|
-
1n,
|
|
317
|
-
config.multiSend,
|
|
318
|
-
multiSendCallData,
|
|
319
|
-
config.safe4337Module,
|
|
320
|
-
"0x0000000000000000000000000000000000000000",
|
|
321
|
-
0n,
|
|
322
|
-
"0x0000000000000000000000000000000000000000"
|
|
323
|
-
]
|
|
324
|
-
});
|
|
325
|
-
const deploymentCode = viem.encodePacked(
|
|
326
|
-
["bytes", "uint256"],
|
|
327
|
-
[SAFE_PROXY_CREATION_CODE, BigInt(config.safeL2Singleton)]
|
|
328
|
-
);
|
|
329
|
-
const salt = viem.keccak256(
|
|
330
|
-
viem.encodePacked(
|
|
331
|
-
["bytes32", "uint256"],
|
|
332
|
-
[viem.keccak256(viem.encodePacked(["bytes"], [initializer])), saltNonce]
|
|
333
|
-
)
|
|
334
|
-
);
|
|
335
|
-
return viem.getContractAddress({
|
|
336
|
-
from: config.safeProxyFactory,
|
|
337
|
-
salt,
|
|
338
|
-
bytecode: deploymentCode,
|
|
339
|
-
opcode: "CREATE2"
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/internal/safe/account.ts
|
|
344
|
-
async function buildSafeAccount(signer, chain) {
|
|
345
|
-
try {
|
|
346
|
-
const publicClient = viem.createPublicClient({
|
|
347
|
-
chain: chains.baseSepolia,
|
|
348
|
-
transport: viem.http(chain.rpcUrl)
|
|
349
|
-
});
|
|
350
|
-
return await accounts.toSafeSmartAccount({
|
|
351
|
-
client: publicClient,
|
|
352
|
-
entryPoint: { address: accountAbstraction.entryPoint07Address, version: "0.7" },
|
|
353
|
-
version: "1.4.1",
|
|
354
|
-
owners: [signer],
|
|
355
|
-
saltNonce: computeSaltNonce2(signer.address),
|
|
356
|
-
safeSingletonAddress: SAFE_L2_SINGLETON,
|
|
357
|
-
safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
|
|
358
|
-
safeModuleSetupAddress: SAFE_MODULE_SETUP,
|
|
359
|
-
safe4337ModuleAddress: SAFE_4337_MODULE,
|
|
360
|
-
safeModules: [],
|
|
361
|
-
setupTransactions: []
|
|
362
|
-
});
|
|
363
|
-
} catch (cause) {
|
|
364
|
-
throw new CapxulError({
|
|
365
|
-
code: "NETWORK_ERROR",
|
|
366
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
367
|
-
cause,
|
|
368
|
-
details: { chainId: chain.chainId }
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function computeSaltNonce2(ownerAddress) {
|
|
373
|
-
return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
|
|
374
|
-
}
|
|
375
|
-
function deriveSafeAddress2(signerAddress) {
|
|
376
|
-
return deriveSafeAddress(signerAddress, defaultSafeDeriveConfig);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// ../platform-kernel/src/ids.ts
|
|
380
|
-
function makePrefixedIdConstructor(prefix, fieldName) {
|
|
381
|
-
const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
|
|
382
|
-
return (raw) => {
|
|
383
|
-
if (typeof raw !== "string" || !re.test(raw)) {
|
|
384
|
-
throw new Error(
|
|
385
|
-
`Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
return raw;
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
var toSafeId = makePrefixedIdConstructor(
|
|
392
|
-
"safe",
|
|
393
|
-
"safeId"
|
|
394
|
-
);
|
|
395
|
-
var toTreasuryId = makePrefixedIdConstructor(
|
|
396
|
-
"try",
|
|
397
|
-
"treasuryId"
|
|
398
|
-
);
|
|
399
|
-
var toOperationId = makePrefixedIdConstructor(
|
|
400
|
-
"op",
|
|
401
|
-
"operationId"
|
|
402
|
-
);
|
|
403
|
-
var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
|
|
404
|
-
var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
|
|
405
|
-
var toSubAccountId = makePrefixedIdConstructor(
|
|
406
|
-
"sub",
|
|
407
|
-
"subAccountId"
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
// src/core/external-accounts.ts
|
|
411
|
-
function brandExternalAccount(raw) {
|
|
412
|
-
return {
|
|
413
|
-
...raw,
|
|
414
|
-
id: toExternalAccountId(raw.id),
|
|
415
|
-
operation: {
|
|
416
|
-
id: toOperationId(raw.operation.id),
|
|
417
|
-
status: raw.operation.status,
|
|
418
|
-
correlationId: toCorrelationId(raw.operation.correlationId)
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function createExternalAccountsClient(config = {}) {
|
|
423
|
-
return {
|
|
424
|
-
retrieve: async (externalAccountId) => {
|
|
425
|
-
if (!config._data) {
|
|
426
|
-
return stub(
|
|
427
|
-
"externalAccounts.retrieve"
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
const [err, raw] = await tryCatch(
|
|
431
|
-
config._data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
432
|
-
externalAccountId
|
|
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: `external_account ${externalAccountId} not found`
|
|
446
|
-
}),
|
|
447
|
-
null
|
|
448
|
-
];
|
|
449
|
-
}
|
|
450
|
-
return [null, brandExternalAccount(raw)];
|
|
451
|
-
},
|
|
452
|
-
remove: async (externalAccountId) => {
|
|
453
|
-
if (!config._data) {
|
|
454
|
-
return stub("externalAccounts.remove");
|
|
455
|
-
}
|
|
456
|
-
const [err] = await tryCatch(
|
|
457
|
-
config._data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
458
|
-
externalAccountId
|
|
459
|
-
})
|
|
460
|
-
);
|
|
461
|
-
if (err) {
|
|
462
|
-
return [
|
|
463
|
-
fromConvexError(err),
|
|
464
|
-
null
|
|
465
|
-
];
|
|
466
|
-
}
|
|
467
|
-
return [null, void 0];
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// src/core/sub-accounts.ts
|
|
473
|
-
function malformedWireError(reason, raw) {
|
|
474
|
-
return new CapxulError({
|
|
475
|
-
code: "PROVIDER_ERROR",
|
|
476
|
-
message: `convex brandSubAccount failed: ${reason}`,
|
|
477
|
-
details: {
|
|
478
|
-
provider: "convex",
|
|
479
|
-
operation: "brandSubAccount",
|
|
480
|
-
reason,
|
|
481
|
-
// PII discipline (`.claude/rules/posthog.md`): user-authored
|
|
482
|
-
// strings on the wire (`name`, `purpose`) are customer-confidential
|
|
483
|
-
// — sub-account names like "Q3 Acquisition Reserve" or
|
|
484
|
-
// "Vendor ABC payments" must not flow into telemetry. We emit a
|
|
485
|
-
// structural keys-only sample via a strict ALLOWLIST so any future
|
|
486
|
-
// wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
|
|
487
|
-
// by construction rather than leaked through a denylist gap.
|
|
488
|
-
sample: safeSampleShape(raw)
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
function safeSampleShape(raw) {
|
|
493
|
-
if (raw === null || typeof raw !== "object") {
|
|
494
|
-
return { type: typeof raw };
|
|
495
|
-
}
|
|
496
|
-
const r = raw;
|
|
497
|
-
const balance = r.balance;
|
|
498
|
-
return {
|
|
499
|
-
object: typeof r.object === "string" ? r.object : typeof r.object,
|
|
500
|
-
idPresent: typeof r.id === "string" && r.id.length > 0,
|
|
501
|
-
// First 4 chars only — enough to distinguish "sub_" prefixed IDs
|
|
502
|
-
// from accidental other resource IDs without leaking the full ID.
|
|
503
|
-
idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
|
|
504
|
-
parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
|
|
505
|
-
status: r.status,
|
|
506
|
-
hasName: typeof r.name === "string",
|
|
507
|
-
hasPurpose: r.purpose !== void 0,
|
|
508
|
-
balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
|
|
509
|
-
createdAtType: typeof r.createdAt,
|
|
510
|
-
updatedAtType: typeof r.updatedAt
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
function isMoneyShape(v) {
|
|
514
|
-
if (typeof v !== "object" || v === null) return false;
|
|
515
|
-
const m = v;
|
|
516
|
-
return typeof m.value === "string" && typeof m.currency === "string";
|
|
517
|
-
}
|
|
518
|
-
function isParentShape(v) {
|
|
519
|
-
if (typeof v !== "object" || v === null) return false;
|
|
520
|
-
const p = v;
|
|
521
|
-
return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
|
|
522
|
-
}
|
|
523
|
-
function isFiniteNonNegativeInteger(v) {
|
|
524
|
-
return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
|
|
525
|
-
}
|
|
526
|
-
function validateWireSubAccount(raw) {
|
|
527
|
-
if (typeof raw !== "object" || raw === null) {
|
|
528
|
-
return { ok: false, reason: "not an object" };
|
|
529
|
-
}
|
|
530
|
-
const r = raw;
|
|
531
|
-
if (r.object !== "sub_account") {
|
|
532
|
-
return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
|
|
533
|
-
}
|
|
534
|
-
if (typeof r.id !== "string" || r.id.length === 0) {
|
|
535
|
-
return { ok: false, reason: "id must be a non-empty string" };
|
|
536
|
-
}
|
|
537
|
-
if (!isParentShape(r.parent)) {
|
|
538
|
-
return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
|
|
539
|
-
}
|
|
540
|
-
if (typeof r.name !== "string") {
|
|
541
|
-
return { ok: false, reason: "name must be a string" };
|
|
542
|
-
}
|
|
543
|
-
if (r.purpose !== void 0 && typeof r.purpose !== "string") {
|
|
544
|
-
return { ok: false, reason: "purpose must be a string when present" };
|
|
545
|
-
}
|
|
546
|
-
if (r.status !== "active" && r.status !== "archived") {
|
|
547
|
-
return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
|
|
548
|
-
}
|
|
549
|
-
if (!isMoneyShape(r.balance)) {
|
|
550
|
-
return { ok: false, reason: "balance must be { value: string, currency: string }" };
|
|
551
|
-
}
|
|
552
|
-
if (!isFiniteNonNegativeInteger(r.createdAt)) {
|
|
553
|
-
return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
|
|
554
|
-
}
|
|
555
|
-
if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
|
|
556
|
-
return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
|
|
557
|
-
}
|
|
558
|
-
return { ok: true, value: r };
|
|
559
|
-
}
|
|
560
|
-
function brandSubAccount(raw) {
|
|
561
|
-
const result = validateWireSubAccount(raw);
|
|
562
|
-
if (!result.ok) {
|
|
563
|
-
throw malformedWireError(result.reason, raw);
|
|
564
|
-
}
|
|
565
|
-
const wire = result.value;
|
|
566
|
-
return {
|
|
567
|
-
object: wire.object,
|
|
568
|
-
id: toSubAccountId(wire.id),
|
|
569
|
-
parent: wire.parent,
|
|
570
|
-
name: wire.name,
|
|
571
|
-
...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
|
|
572
|
-
status: wire.status,
|
|
573
|
-
balance: wire.balance,
|
|
574
|
-
createdAt: new Date(wire.createdAt).toISOString()
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function tryBrandSubAccount(raw) {
|
|
578
|
-
try {
|
|
579
|
-
return [null, brandSubAccount(raw)];
|
|
580
|
-
} catch (err) {
|
|
581
|
-
if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
|
|
582
|
-
return [err, null];
|
|
583
|
-
}
|
|
584
|
-
return [
|
|
585
|
-
malformedWireError(
|
|
586
|
-
err instanceof Error ? err.message : String(err),
|
|
587
|
-
raw
|
|
588
|
-
),
|
|
589
|
-
null
|
|
590
|
-
];
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
function createSubAccountsClient(config = {}) {
|
|
594
|
-
return {
|
|
595
|
-
retrieve: async (subAccountId) => {
|
|
596
|
-
if (!config._data) {
|
|
597
|
-
return stub("subAccounts.retrieve");
|
|
598
|
-
}
|
|
599
|
-
const [err, raw] = await tryCatch(
|
|
600
|
-
config._data.query(api.subAccounts.queries.retrieve, {
|
|
601
|
-
subAccountId
|
|
602
|
-
})
|
|
603
|
-
);
|
|
604
|
-
if (err) {
|
|
605
|
-
return [
|
|
606
|
-
fromConvexError(err),
|
|
607
|
-
null
|
|
608
|
-
];
|
|
609
|
-
}
|
|
610
|
-
if (!raw) {
|
|
611
|
-
return [
|
|
612
|
-
new CapxulError({
|
|
613
|
-
code: "NOT_FOUND",
|
|
614
|
-
message: `sub_account ${subAccountId} not found`
|
|
615
|
-
}),
|
|
616
|
-
null
|
|
617
|
-
];
|
|
618
|
-
}
|
|
619
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
620
|
-
if (brandErr) {
|
|
621
|
-
return [brandErr, null];
|
|
622
|
-
}
|
|
623
|
-
return [null, branded];
|
|
624
|
-
},
|
|
625
|
-
remove: async (subAccountId) => {
|
|
626
|
-
if (!config._data) {
|
|
627
|
-
return stub("subAccounts.remove");
|
|
628
|
-
}
|
|
629
|
-
const [err, raw] = await tryCatch(
|
|
630
|
-
config._data.mutation(api.subAccounts.mutations.archive, {
|
|
631
|
-
subAccountId
|
|
632
|
-
})
|
|
633
|
-
);
|
|
634
|
-
if (err) {
|
|
635
|
-
return [
|
|
636
|
-
fromConvexError(err),
|
|
637
|
-
null
|
|
638
|
-
];
|
|
639
|
-
}
|
|
640
|
-
if (!raw) {
|
|
641
|
-
return [
|
|
642
|
-
new CapxulError({
|
|
643
|
-
code: "NOT_FOUND",
|
|
644
|
-
message: `sub_account ${subAccountId} not found`
|
|
645
|
-
}),
|
|
646
|
-
null
|
|
647
|
-
];
|
|
648
|
-
}
|
|
649
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
650
|
-
if (brandErr) {
|
|
651
|
-
return [brandErr, null];
|
|
652
|
-
}
|
|
653
|
-
return [null, branded];
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// src/core/accounts.ts
|
|
659
|
-
function createAccountExternalAccountsClient(config) {
|
|
660
|
-
return {
|
|
661
|
-
create: async (input) => {
|
|
662
|
-
if (!config._data) {
|
|
663
|
-
return stub(
|
|
664
|
-
"accounts.externalAccounts.create"
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
const [err, raw] = await tryCatch(
|
|
668
|
-
config._data.mutation(
|
|
669
|
-
api.externalAccounts.mutations.createPersonal,
|
|
670
|
-
{
|
|
671
|
-
kind: input.kind,
|
|
672
|
-
label: input.label,
|
|
673
|
-
address: input.address,
|
|
674
|
-
iban: input.iban,
|
|
675
|
-
bic: input.bic,
|
|
676
|
-
accountHolder: input.accountHolder,
|
|
677
|
-
network: input.network,
|
|
678
|
-
panToken: input.panToken,
|
|
679
|
-
last4: input.last4
|
|
680
|
-
}
|
|
681
|
-
)
|
|
682
|
-
);
|
|
683
|
-
if (err) {
|
|
684
|
-
return [
|
|
685
|
-
fromConvexError(err),
|
|
686
|
-
null
|
|
687
|
-
];
|
|
688
|
-
}
|
|
689
|
-
if (!raw) {
|
|
690
|
-
return [
|
|
691
|
-
new CapxulError({
|
|
692
|
-
code: "NOT_FOUND",
|
|
693
|
-
message: "external_account creation returned no resource"
|
|
694
|
-
}),
|
|
695
|
-
null
|
|
696
|
-
];
|
|
697
|
-
}
|
|
698
|
-
return [
|
|
699
|
-
null,
|
|
700
|
-
brandExternalAccount(
|
|
701
|
-
raw
|
|
702
|
-
)
|
|
703
|
-
];
|
|
704
|
-
},
|
|
705
|
-
list: async (input) => {
|
|
706
|
-
if (!config._data) {
|
|
707
|
-
return stub(
|
|
708
|
-
"accounts.externalAccounts.list"
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
const [err, result] = await tryCatch(
|
|
712
|
-
config._data.query(api.externalAccounts.queries.listPersonal, {
|
|
713
|
-
limit: input.limit,
|
|
714
|
-
cursor: input.cursor
|
|
715
|
-
})
|
|
716
|
-
);
|
|
717
|
-
if (err) {
|
|
718
|
-
return [fromConvexError(err), null];
|
|
719
|
-
}
|
|
720
|
-
const branded = result.data.map(
|
|
721
|
-
(row) => brandExternalAccount(
|
|
722
|
-
row
|
|
723
|
-
)
|
|
724
|
-
);
|
|
725
|
-
return [
|
|
726
|
-
null,
|
|
727
|
-
{
|
|
728
|
-
object: "list",
|
|
729
|
-
data: branded,
|
|
730
|
-
page: result.page
|
|
731
|
-
}
|
|
732
|
-
];
|
|
733
|
-
},
|
|
734
|
-
retrieve: async (externalAccountId) => {
|
|
735
|
-
if (!config._data) {
|
|
736
|
-
return stub(
|
|
737
|
-
"accounts.externalAccounts.retrieve"
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
const [err, raw] = await tryCatch(
|
|
741
|
-
config._data.query(api.externalAccounts.queries.retrievePersonal, {
|
|
742
|
-
externalAccountId
|
|
743
|
-
})
|
|
744
|
-
);
|
|
745
|
-
if (err) {
|
|
746
|
-
return [
|
|
747
|
-
fromConvexError(err),
|
|
748
|
-
null
|
|
749
|
-
];
|
|
750
|
-
}
|
|
751
|
-
if (!raw) {
|
|
752
|
-
return [
|
|
753
|
-
new CapxulError({
|
|
754
|
-
code: "NOT_FOUND",
|
|
755
|
-
message: `external_account ${externalAccountId} not found`
|
|
756
|
-
}),
|
|
757
|
-
null
|
|
758
|
-
];
|
|
759
|
-
}
|
|
760
|
-
return [
|
|
761
|
-
null,
|
|
762
|
-
brandExternalAccount(
|
|
763
|
-
raw
|
|
764
|
-
)
|
|
765
|
-
];
|
|
766
|
-
},
|
|
767
|
-
remove: async (externalAccountId) => {
|
|
768
|
-
if (!config._data) {
|
|
769
|
-
return stub("accounts.externalAccounts.remove");
|
|
770
|
-
}
|
|
771
|
-
const [err] = await tryCatch(
|
|
772
|
-
config._data.mutation(api.externalAccounts.mutations.removePersonal, {
|
|
773
|
-
externalAccountId
|
|
774
|
-
})
|
|
775
|
-
);
|
|
776
|
-
if (err) {
|
|
777
|
-
return [
|
|
778
|
-
fromConvexError(err),
|
|
779
|
-
null
|
|
780
|
-
];
|
|
781
|
-
}
|
|
782
|
-
return [null, void 0];
|
|
783
|
-
}
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function createAccountSubAccountsClient(config) {
|
|
787
|
-
return {
|
|
788
|
-
create: async (input) => {
|
|
789
|
-
if (!config._data) {
|
|
790
|
-
return stub(
|
|
791
|
-
"accounts.subAccounts.create"
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
const [err, raw] = await tryCatch(
|
|
795
|
-
config._data.mutation(api.subAccounts.mutations.create, {
|
|
796
|
-
parent: { kind: "account", id: input.accountId },
|
|
797
|
-
name: input.name,
|
|
798
|
-
purpose: input.purpose
|
|
799
|
-
})
|
|
800
|
-
);
|
|
801
|
-
if (err) {
|
|
802
|
-
return [
|
|
803
|
-
fromConvexError(err),
|
|
804
|
-
null
|
|
805
|
-
];
|
|
806
|
-
}
|
|
807
|
-
if (!raw) {
|
|
808
|
-
return [
|
|
809
|
-
new CapxulError({
|
|
810
|
-
code: "NOT_FOUND",
|
|
811
|
-
message: "sub_account creation returned no resource"
|
|
812
|
-
}),
|
|
813
|
-
null
|
|
814
|
-
];
|
|
815
|
-
}
|
|
816
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
817
|
-
if (brandErr) {
|
|
818
|
-
return [
|
|
819
|
-
brandErr,
|
|
820
|
-
null
|
|
821
|
-
];
|
|
822
|
-
}
|
|
823
|
-
return [null, branded];
|
|
824
|
-
},
|
|
825
|
-
list: async (input) => {
|
|
826
|
-
if (!config._data) {
|
|
827
|
-
return stub(
|
|
828
|
-
"accounts.subAccounts.list"
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
const [err, rows] = await tryCatch(
|
|
832
|
-
config._data.query(api.subAccounts.queries.listByAccount, {
|
|
833
|
-
accountId: input.accountId
|
|
834
|
-
})
|
|
835
|
-
);
|
|
836
|
-
if (err) {
|
|
837
|
-
return [
|
|
838
|
-
fromConvexError(err),
|
|
839
|
-
null
|
|
840
|
-
];
|
|
841
|
-
}
|
|
842
|
-
const branded = [];
|
|
843
|
-
for (const row of rows) {
|
|
844
|
-
const [brandErr, value] = tryBrandSubAccount(row);
|
|
845
|
-
if (brandErr) {
|
|
846
|
-
return [
|
|
847
|
-
brandErr,
|
|
848
|
-
null
|
|
849
|
-
];
|
|
850
|
-
}
|
|
851
|
-
branded.push(value);
|
|
852
|
-
}
|
|
853
|
-
return [
|
|
854
|
-
null,
|
|
855
|
-
{
|
|
856
|
-
object: "list",
|
|
857
|
-
data: branded,
|
|
858
|
-
page: { hasMore: false }
|
|
859
|
-
}
|
|
860
|
-
];
|
|
861
|
-
},
|
|
862
|
-
retrieve: async (subAccountId) => {
|
|
863
|
-
if (!config._data) {
|
|
864
|
-
return stub(
|
|
865
|
-
"accounts.subAccounts.retrieve"
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
const [err, raw] = await tryCatch(
|
|
869
|
-
config._data.query(api.subAccounts.queries.retrieve, {
|
|
870
|
-
subAccountId
|
|
871
|
-
})
|
|
872
|
-
);
|
|
873
|
-
if (err) {
|
|
874
|
-
return [
|
|
875
|
-
fromConvexError(err),
|
|
876
|
-
null
|
|
877
|
-
];
|
|
878
|
-
}
|
|
879
|
-
if (!raw) {
|
|
880
|
-
return [
|
|
881
|
-
new CapxulError({
|
|
882
|
-
code: "NOT_FOUND",
|
|
883
|
-
message: `sub_account ${subAccountId} not found`
|
|
884
|
-
}),
|
|
885
|
-
null
|
|
886
|
-
];
|
|
887
|
-
}
|
|
888
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
889
|
-
if (brandErr) {
|
|
890
|
-
return [
|
|
891
|
-
brandErr,
|
|
892
|
-
null
|
|
893
|
-
];
|
|
894
|
-
}
|
|
895
|
-
return [null, branded];
|
|
896
|
-
},
|
|
897
|
-
remove: async (subAccountId) => {
|
|
898
|
-
if (!config._data) {
|
|
899
|
-
return stub(
|
|
900
|
-
"accounts.subAccounts.remove"
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
const [err, raw] = await tryCatch(
|
|
904
|
-
config._data.mutation(api.subAccounts.mutations.archive, {
|
|
905
|
-
subAccountId
|
|
906
|
-
})
|
|
907
|
-
);
|
|
908
|
-
if (err) {
|
|
909
|
-
return [
|
|
910
|
-
fromConvexError(err),
|
|
911
|
-
null
|
|
912
|
-
];
|
|
913
|
-
}
|
|
914
|
-
if (!raw) {
|
|
915
|
-
return [
|
|
916
|
-
new CapxulError({
|
|
917
|
-
code: "NOT_FOUND",
|
|
918
|
-
message: `sub_account ${subAccountId} not found`
|
|
919
|
-
}),
|
|
920
|
-
null
|
|
921
|
-
];
|
|
922
|
-
}
|
|
923
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
924
|
-
if (brandErr) {
|
|
925
|
-
return [
|
|
926
|
-
brandErr,
|
|
927
|
-
null
|
|
928
|
-
];
|
|
929
|
-
}
|
|
930
|
-
return [null, branded];
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
function createAccountsClient(config = {}) {
|
|
935
|
-
return {
|
|
936
|
-
retrieve: async (accountId) => {
|
|
937
|
-
if (!config._data) {
|
|
938
|
-
return stub("accounts.retrieve");
|
|
939
|
-
}
|
|
940
|
-
try {
|
|
941
|
-
const account = await config._data.query(
|
|
942
|
-
api.openfort.queries.getMyAccount,
|
|
943
|
-
{}
|
|
944
|
-
);
|
|
945
|
-
if (account.id !== accountId) {
|
|
946
|
-
return [
|
|
947
|
-
new CapxulError({
|
|
948
|
-
code: "PERMISSION_DENIED",
|
|
949
|
-
message: "accounts.retrieve currently supports the authenticated caller's own account only.",
|
|
950
|
-
details: {
|
|
951
|
-
requestedAccountId: accountId,
|
|
952
|
-
authenticatedAccountId: account.id
|
|
953
|
-
}
|
|
954
|
-
}),
|
|
955
|
-
null
|
|
956
|
-
];
|
|
957
|
-
}
|
|
958
|
-
return [null, account];
|
|
959
|
-
} catch (cause) {
|
|
960
|
-
return [fromConvexError(cause), null];
|
|
961
|
-
}
|
|
962
|
-
},
|
|
963
|
-
lookup: async () => stub("accounts.lookup"),
|
|
964
|
-
update: async (input) => {
|
|
965
|
-
if (!config._data) {
|
|
966
|
-
return stub("accounts.update");
|
|
967
|
-
}
|
|
968
|
-
if (input.countryCode !== void 0) {
|
|
969
|
-
return [
|
|
970
|
-
new CapxulError({
|
|
971
|
-
code: "INVALID_INPUT",
|
|
972
|
-
message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
973
|
-
details: { field: "countryCode" }
|
|
974
|
-
}),
|
|
975
|
-
null
|
|
976
|
-
];
|
|
977
|
-
}
|
|
978
|
-
try {
|
|
979
|
-
const current = await config._data.query(
|
|
980
|
-
api.openfort.queries.getMyAccount,
|
|
981
|
-
{}
|
|
982
|
-
);
|
|
983
|
-
if (current.id !== input.accountId) {
|
|
984
|
-
return [
|
|
985
|
-
new CapxulError({
|
|
986
|
-
code: "PERMISSION_DENIED",
|
|
987
|
-
message: "accounts.update currently supports the authenticated caller's own account only.",
|
|
988
|
-
details: {
|
|
989
|
-
requestedAccountId: input.accountId,
|
|
990
|
-
authenticatedAccountId: current.id
|
|
991
|
-
}
|
|
992
|
-
}),
|
|
993
|
-
null
|
|
994
|
-
];
|
|
995
|
-
}
|
|
996
|
-
await config._data.mutation(api.openfort.mutations.updateProfile, {
|
|
997
|
-
displayName: input.name,
|
|
998
|
-
username: input.username
|
|
999
|
-
});
|
|
1000
|
-
const updated = await config._data.query(
|
|
1001
|
-
api.openfort.queries.getMyAccount,
|
|
1002
|
-
{}
|
|
1003
|
-
);
|
|
1004
|
-
return [null, updated];
|
|
1005
|
-
} catch (cause) {
|
|
1006
|
-
return [fromConvexError(cause), null];
|
|
1007
|
-
}
|
|
1008
|
-
},
|
|
1009
|
-
provisionPersonal: async (input) => {
|
|
1010
|
-
if (!config._data) {
|
|
1011
|
-
return stub(
|
|
1012
|
-
"accounts.provisionPersonal"
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
if (input.signerProvider.kind !== "local-private-key") {
|
|
1016
|
-
return [
|
|
1017
|
-
new CapxulError({
|
|
1018
|
-
code: "INVALID_INPUT",
|
|
1019
|
-
message: "accounts.provisionPersonal currently supports local-private-key signer providers only."
|
|
1020
|
-
}),
|
|
1021
|
-
null
|
|
1022
|
-
];
|
|
1023
|
-
}
|
|
1024
|
-
try {
|
|
1025
|
-
await config._data.mutation(
|
|
1026
|
-
api.safe.mutations.provisionLocalPersonalAccount,
|
|
1027
|
-
{
|
|
1028
|
-
displayName: input.displayName,
|
|
1029
|
-
username: input.username,
|
|
1030
|
-
countryCode: input.countryCode,
|
|
1031
|
-
eoaAddress: input.signerProvider.signerAddress,
|
|
1032
|
-
safeAddress: deriveSafeAddress2(input.signerProvider.signerAddress)
|
|
1033
|
-
}
|
|
1034
|
-
);
|
|
1035
|
-
const account = await config._data.query(
|
|
1036
|
-
api.openfort.queries.getMyAccount,
|
|
1037
|
-
{}
|
|
1038
|
-
);
|
|
1039
|
-
if (!account) {
|
|
1040
|
-
return [
|
|
1041
|
-
new CapxulError({
|
|
1042
|
-
code: "NOT_FOUND",
|
|
1043
|
-
message: "accounts.provisionPersonal completed but no account resource was readable."
|
|
1044
|
-
}),
|
|
1045
|
-
null
|
|
1046
|
-
];
|
|
1047
|
-
}
|
|
1048
|
-
return [null, account];
|
|
1049
|
-
} catch (cause) {
|
|
1050
|
-
return [
|
|
1051
|
-
fromConvexError(cause),
|
|
1052
|
-
null
|
|
1053
|
-
];
|
|
1054
|
-
}
|
|
1055
|
-
},
|
|
1056
|
-
safes: {
|
|
1057
|
-
retrieve: async (safeId) => {
|
|
1058
|
-
if (!config._data) {
|
|
1059
|
-
return stub("accounts.safes.retrieve");
|
|
1060
|
-
}
|
|
1061
|
-
try {
|
|
1062
|
-
const safe = await config._data.query(
|
|
1063
|
-
api.safe.queries.retrieveAccountSafe,
|
|
1064
|
-
{ safeId }
|
|
1065
|
-
);
|
|
1066
|
-
if (!safe) {
|
|
1067
|
-
return [
|
|
1068
|
-
new CapxulError({
|
|
1069
|
-
code: "NOT_FOUND",
|
|
1070
|
-
message: `safe ${safeId} not found`
|
|
1071
|
-
}),
|
|
1072
|
-
null
|
|
1073
|
-
];
|
|
1074
|
-
}
|
|
1075
|
-
return [null, safe];
|
|
1076
|
-
} catch (cause) {
|
|
1077
|
-
return [
|
|
1078
|
-
fromConvexError(cause),
|
|
1079
|
-
null
|
|
1080
|
-
];
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
},
|
|
1084
|
-
kycProfiles: {
|
|
1085
|
-
create: async () => stub("accounts.kycProfiles.create"),
|
|
1086
|
-
retrieve: async () => stub("accounts.kycProfiles.retrieve")
|
|
1087
|
-
},
|
|
1088
|
-
externalAccounts: createAccountExternalAccountsClient(config),
|
|
1089
|
-
subAccounts: createAccountSubAccountsClient(config),
|
|
1090
|
-
balanceLedger: {
|
|
1091
|
-
list: async (input) => {
|
|
1092
|
-
if (!config._data) {
|
|
1093
|
-
return stub(
|
|
1094
|
-
"accounts.balanceLedger.list"
|
|
1095
|
-
);
|
|
1096
|
-
}
|
|
1097
|
-
try {
|
|
1098
|
-
const accountId = input.accountId.replace(/^acct_/, "");
|
|
1099
|
-
const page = await config._data.query(
|
|
1100
|
-
api.balanceLedger.queries.listForAccount,
|
|
1101
|
-
{ accountId, limit: input.limit, cursor: input.cursor }
|
|
1102
|
-
);
|
|
1103
|
-
return [null, page];
|
|
1104
|
-
} catch (cause) {
|
|
1105
|
-
return [fromConvexError(cause), null];
|
|
1106
|
-
}
|
|
1107
|
-
},
|
|
1108
|
-
retrieve: async (entryId) => {
|
|
1109
|
-
if (!config._data) {
|
|
1110
|
-
return stub(
|
|
1111
|
-
"accounts.balanceLedger.retrieve"
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
1114
|
-
try {
|
|
1115
|
-
const entry = await config._data.query(
|
|
1116
|
-
api.balanceLedger.queries.retrieve,
|
|
1117
|
-
{ entryId }
|
|
1118
|
-
);
|
|
1119
|
-
if (!entry) {
|
|
1120
|
-
return [
|
|
1121
|
-
new CapxulError({
|
|
1122
|
-
code: "NOT_FOUND",
|
|
1123
|
-
message: `balance_ledger_entry ${entryId} not found`
|
|
1124
|
-
}),
|
|
1125
|
-
null
|
|
1126
|
-
];
|
|
1127
|
-
}
|
|
1128
|
-
return [null, entry];
|
|
1129
|
-
} catch (cause) {
|
|
1130
|
-
return [fromConvexError(cause), null];
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// src/core/api-keys.ts
|
|
1138
|
-
function createApiKeysClient() {
|
|
1139
|
-
return {
|
|
1140
|
-
create: async () => stub("apiKeys.create"),
|
|
1141
|
-
retrieve: async () => stub("apiKeys.retrieve"),
|
|
1142
|
-
list: async () => stub("apiKeys.list"),
|
|
1143
|
-
revoke: async () => stub("apiKeys.revoke")
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
function createDefaultDataClient(convexUrl, jwt) {
|
|
1147
|
-
const client = new browser.ConvexHttpClient(convexUrl);
|
|
1148
|
-
client.setAuth(jwt);
|
|
1149
|
-
return client;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// src/transport.ts
|
|
1153
|
-
function makeHttpTransport(config) {
|
|
1154
|
-
switch (config.mode) {
|
|
1155
|
-
case "build-time-urls":
|
|
1156
|
-
return makeBuildTimeUrlsTransport(config);
|
|
1157
|
-
case "publishable-key":
|
|
1158
|
-
return makePublishableKeyTransport(config);
|
|
1159
|
-
default:
|
|
1160
|
-
return assertNever(config);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
function createLifecycle(initial) {
|
|
1164
|
-
let state = initial;
|
|
1165
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1166
|
-
return {
|
|
1167
|
-
getState: () => state,
|
|
1168
|
-
setState: (next) => {
|
|
1169
|
-
if (Object.is(state, next)) return;
|
|
1170
|
-
state = next;
|
|
1171
|
-
for (const listener of listeners) listener();
|
|
1172
|
-
},
|
|
1173
|
-
subscribe: (listener) => {
|
|
1174
|
-
listeners.add(listener);
|
|
1175
|
-
return () => {
|
|
1176
|
-
listeners.delete(listener);
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
function makeBuildTimeUrlsTransport(config) {
|
|
1182
|
-
if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
|
|
1183
|
-
throw invalidConfigError(
|
|
1184
|
-
"authBaseUrl",
|
|
1185
|
-
"build-time-urls transport requires a non-empty authBaseUrl."
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
if (!config.convexUrl || config.convexUrl.trim().length === 0) {
|
|
1189
|
-
throw invalidConfigError(
|
|
1190
|
-
"convexUrl",
|
|
1191
|
-
"build-time-urls transport requires a non-empty convexUrl."
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
const authBaseUrl = stripTrailingSlash(config.authBaseUrl);
|
|
1195
|
-
const convexUrl = config.convexUrl;
|
|
1196
|
-
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1197
|
-
const runtime = { authBaseUrl, convexUrl };
|
|
1198
|
-
const lifecycle = createLifecycle({ status: "ready", runtime });
|
|
1199
|
-
let dataClient = null;
|
|
1200
|
-
return {
|
|
1201
|
-
authBaseUrl,
|
|
1202
|
-
convexUrl,
|
|
1203
|
-
ensureRuntime: async () => runtime,
|
|
1204
|
-
fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
|
|
1205
|
-
getState: lifecycle.getState,
|
|
1206
|
-
subscribe: lifecycle.subscribe,
|
|
1207
|
-
getDataClient: () => dataClient,
|
|
1208
|
-
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
1209
|
-
if (nextDataClient !== void 0) dataClient = nextDataClient;
|
|
1210
|
-
lifecycle.setState({ status: "authenticated", runtime });
|
|
1211
|
-
},
|
|
1212
|
-
clearAuth: () => {
|
|
1213
|
-
dataClient = null;
|
|
1214
|
-
lifecycle.setState({ status: "ready", runtime });
|
|
1215
|
-
}
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
function makePublishableKeyTransport(config) {
|
|
1219
|
-
const publishableKey = config.publishableKey?.trim();
|
|
1220
|
-
if (!publishableKey) {
|
|
1221
|
-
throw invalidConfigError(
|
|
1222
|
-
"publishableKey",
|
|
1223
|
-
"publishable-key transport requires a non-empty publishableKey."
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1227
|
-
const bootstrapUrl = normalizeBootstrapUrl(
|
|
1228
|
-
config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
|
|
1229
|
-
);
|
|
1230
|
-
let authBaseUrl = "";
|
|
1231
|
-
let convexUrl = "";
|
|
1232
|
-
let bootstrapPromise = null;
|
|
1233
|
-
let dataClient = null;
|
|
1234
|
-
const lifecycle = createLifecycle({ status: "idle" });
|
|
1235
|
-
async function ensureBootstrap() {
|
|
1236
|
-
if (bootstrapPromise) return await bootstrapPromise;
|
|
1237
|
-
lifecycle.setState({ status: "bootstrapping" });
|
|
1238
|
-
const attempt = (async () => {
|
|
1239
|
-
const response = await fetchImpl(bootstrapUrl, {
|
|
1240
|
-
method: "POST",
|
|
1241
|
-
headers: { "content-type": "application/json" },
|
|
1242
|
-
body: JSON.stringify({ publishableKey })
|
|
1243
|
-
});
|
|
1244
|
-
if (!response.ok) {
|
|
1245
|
-
throw await bootstrapResponseError(response, bootstrapUrl);
|
|
1246
|
-
}
|
|
1247
|
-
const body = await readBootstrapSuccessBody(response);
|
|
1248
|
-
if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
|
|
1249
|
-
throw bootstrapContractError(
|
|
1250
|
-
"authBaseUrl",
|
|
1251
|
-
"/v1/client/bootstrap returned no authBaseUrl."
|
|
1252
|
-
);
|
|
1253
|
-
}
|
|
1254
|
-
if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
|
|
1255
|
-
throw bootstrapContractError(
|
|
1256
|
-
"convexUrl",
|
|
1257
|
-
"/v1/client/bootstrap returned no convexUrl."
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
authBaseUrl = stripTrailingSlash(body.authBaseUrl);
|
|
1261
|
-
convexUrl = body.convexUrl;
|
|
1262
|
-
const runtime = { authBaseUrl, convexUrl };
|
|
1263
|
-
lifecycle.setState({ status: "ready", runtime });
|
|
1264
|
-
return runtime;
|
|
1265
|
-
})();
|
|
1266
|
-
bootstrapPromise = attempt.catch((err) => {
|
|
1267
|
-
const error = normalizeBootstrapThrownError(err);
|
|
1268
|
-
bootstrapPromise = null;
|
|
1269
|
-
lifecycle.setState({
|
|
1270
|
-
status: "error",
|
|
1271
|
-
error
|
|
1272
|
-
});
|
|
1273
|
-
throw error;
|
|
1274
|
-
});
|
|
1275
|
-
return await bootstrapPromise;
|
|
1276
|
-
}
|
|
1277
|
-
return {
|
|
1278
|
-
get authBaseUrl() {
|
|
1279
|
-
return authBaseUrl;
|
|
1280
|
-
},
|
|
1281
|
-
get convexUrl() {
|
|
1282
|
-
return convexUrl;
|
|
1283
|
-
},
|
|
1284
|
-
ensureRuntime: ensureBootstrap,
|
|
1285
|
-
fetch: async (path, init) => {
|
|
1286
|
-
const resolved = await ensureBootstrap();
|
|
1287
|
-
return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
|
|
1288
|
-
},
|
|
1289
|
-
getState: lifecycle.getState,
|
|
1290
|
-
subscribe: lifecycle.subscribe,
|
|
1291
|
-
getDataClient: () => dataClient,
|
|
1292
|
-
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
1293
|
-
const current = lifecycle.getState();
|
|
1294
|
-
if (current.status !== "ready" && current.status !== "authenticated") {
|
|
1295
|
-
throw internalTransportError(
|
|
1296
|
-
`markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1299
|
-
if (nextDataClient !== void 0) dataClient = nextDataClient;
|
|
1300
|
-
lifecycle.setState({
|
|
1301
|
-
status: "authenticated",
|
|
1302
|
-
runtime: current.runtime
|
|
1303
|
-
});
|
|
1304
|
-
},
|
|
1305
|
-
clearAuth: () => {
|
|
1306
|
-
const current = lifecycle.getState();
|
|
1307
|
-
dataClient = null;
|
|
1308
|
-
if (current.status === "authenticated") {
|
|
1309
|
-
lifecycle.setState({ status: "ready", runtime: current.runtime });
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
function stripTrailingSlash(url) {
|
|
1315
|
-
return url.replace(/\/+$/, "");
|
|
1316
|
-
}
|
|
1317
|
-
function normalizeBootstrapUrl(url) {
|
|
1318
|
-
const normalized = stripTrailingSlash(url.trim());
|
|
1319
|
-
if (!isAbsoluteHttpUrl(normalized)) {
|
|
1320
|
-
throw invalidConfigError(
|
|
1321
|
-
"bootstrapUrl",
|
|
1322
|
-
"publishable-key transport requires an absolute http(s) bootstrapUrl."
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
return normalized;
|
|
1326
|
-
}
|
|
1327
|
-
function isAbsoluteHttpUrl(url) {
|
|
1328
|
-
try {
|
|
1329
|
-
const parsed = new URL(url);
|
|
1330
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
1331
|
-
} catch {
|
|
1332
|
-
return false;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
function resolveUrl(authBaseUrl, path) {
|
|
1336
|
-
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
1337
|
-
return path;
|
|
1338
|
-
}
|
|
1339
|
-
return `${authBaseUrl}${path}`;
|
|
1340
|
-
}
|
|
1341
|
-
function assertNever(value) {
|
|
1342
|
-
throw internalTransportError(
|
|
1343
|
-
`Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1346
|
-
function invalidConfigError(field, reason) {
|
|
1347
|
-
return new CapxulError({
|
|
1348
|
-
code: "INVALID_INPUT",
|
|
1349
|
-
message: `Invalid ${field}: ${reason}`,
|
|
1350
|
-
details: { source: "sdk-config", field, reason }
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
function bootstrapContractError(field, message) {
|
|
1354
|
-
return new CapxulError({
|
|
1355
|
-
code: "INVALID_INPUT",
|
|
1356
|
-
message,
|
|
1357
|
-
details: {
|
|
1358
|
-
source: "backend-bootstrap",
|
|
1359
|
-
phase: "publishable-key-bootstrap",
|
|
1360
|
-
field,
|
|
1361
|
-
reason: message
|
|
1362
|
-
}
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
function internalTransportError(reason) {
|
|
1366
|
-
return new CapxulError({
|
|
1367
|
-
code: "INTERNAL_ERROR",
|
|
1368
|
-
message: `Internal error: ${reason}`,
|
|
1369
|
-
details: { source: "sdk-transport", reason }
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
async function bootstrapResponseError(response, bootstrapUrl) {
|
|
1373
|
-
const envelope = await readBootstrapErrorEnvelope(response);
|
|
1374
|
-
const wireCode = readNonEmptyString(envelope?.error?.code);
|
|
1375
|
-
const normalized = normalizeBootstrapErrorCode(wireCode);
|
|
1376
|
-
const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
|
|
1377
|
-
const backendDetails = readRecord(envelope?.error?.details);
|
|
1378
|
-
return new CapxulError({
|
|
1379
|
-
code: normalized.code,
|
|
1380
|
-
message,
|
|
1381
|
-
details: {
|
|
1382
|
-
...backendDetails,
|
|
1383
|
-
source: "backend-bootstrap",
|
|
1384
|
-
phase: "publishable-key-bootstrap",
|
|
1385
|
-
httpStatus: response.status,
|
|
1386
|
-
...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
|
|
1387
|
-
},
|
|
1388
|
-
operationId: readNonEmptyString(envelope?.error?.operationId),
|
|
1389
|
-
correlationId: readNonEmptyString(envelope?.error?.correlationId),
|
|
1390
|
-
retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
async function readBootstrapErrorEnvelope(response) {
|
|
1394
|
-
try {
|
|
1395
|
-
const parsed = await response.json();
|
|
1396
|
-
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1397
|
-
} catch {
|
|
1398
|
-
return null;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
async function readBootstrapSuccessBody(response) {
|
|
1402
|
-
try {
|
|
1403
|
-
const parsed = await response.json();
|
|
1404
|
-
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1405
|
-
} catch {
|
|
1406
|
-
throw bootstrapContractError(
|
|
1407
|
-
"body",
|
|
1408
|
-
"/v1/client/bootstrap returned invalid JSON."
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
function normalizeBootstrapThrownError(error) {
|
|
1413
|
-
if (error instanceof CapxulError) return error;
|
|
1414
|
-
return new CapxulError({
|
|
1415
|
-
code: "NETWORK_ERROR",
|
|
1416
|
-
message: "Publishable-key bootstrap network failure.",
|
|
1417
|
-
cause: error,
|
|
1418
|
-
details: {
|
|
1419
|
-
source: "bootstrap-network",
|
|
1420
|
-
phase: "publishable-key-bootstrap"
|
|
1421
|
-
}
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
function normalizeBootstrapErrorCode(wireCode) {
|
|
1425
|
-
if (wireCode === "INTERNAL_SERVER_ERROR") {
|
|
1426
|
-
return { code: "INTERNAL_ERROR", wireCode };
|
|
1427
|
-
}
|
|
1428
|
-
if (wireCode && isCapxulErrorCode(wireCode)) {
|
|
1429
|
-
return { code: wireCode };
|
|
1430
|
-
}
|
|
1431
|
-
return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
|
|
1432
|
-
}
|
|
1433
|
-
var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1434
|
-
"NOT_AUTHENTICATED",
|
|
1435
|
-
"EMAIL_DELIVERY_FAILED",
|
|
1436
|
-
"PROFILE_NOT_FOUND",
|
|
1437
|
-
"SMART_ACCOUNT_MISSING",
|
|
1438
|
-
"PLAYER_NOT_FOUND",
|
|
1439
|
-
"ACCOUNT_NOT_FOUND",
|
|
1440
|
-
"PROVIDER_ERROR",
|
|
1441
|
-
"INVALID_INPUT",
|
|
1442
|
-
"ENV_MISSING",
|
|
1443
|
-
"NOT_IMPLEMENTED",
|
|
1444
|
-
"VERIFICATION_REQUIRED",
|
|
1445
|
-
"INSUFFICIENT_BALANCE",
|
|
1446
|
-
"INVALID_RECIPIENT",
|
|
1447
|
-
"TRANSACTION_FAILED",
|
|
1448
|
-
"RATE_LIMITED",
|
|
1449
|
-
"NETWORK_ERROR",
|
|
1450
|
-
"UNKNOWN",
|
|
1451
|
-
"PERMISSION_DENIED",
|
|
1452
|
-
"API_KEY_INVALID",
|
|
1453
|
-
"API_KEY_EXPIRED",
|
|
1454
|
-
"IDEMPOTENCY_CONFLICT",
|
|
1455
|
-
"NOT_FOUND",
|
|
1456
|
-
"OPERATION_CANCELED",
|
|
1457
|
-
"OPERATION_TIMEOUT",
|
|
1458
|
-
"ACTION_REQUIRED",
|
|
1459
|
-
"KYC_REQUIRED",
|
|
1460
|
-
"POLICY_DENIED",
|
|
1461
|
-
"SAFE_NOT_READY",
|
|
1462
|
-
"PROVIDER_UNAVAILABLE",
|
|
1463
|
-
"PROVIDER_REJECTED",
|
|
1464
|
-
"RECONCILIATION_FAILED",
|
|
1465
|
-
"INTERNAL_ERROR",
|
|
1466
|
-
"QUOTE_EXPIRED",
|
|
1467
|
-
"QUOTE_NOT_FOUND"
|
|
1468
|
-
]);
|
|
1469
|
-
function isCapxulErrorCode(value) {
|
|
1470
|
-
return CAPXUL_ERROR_CODES.has(value);
|
|
1471
|
-
}
|
|
1472
|
-
function readRecord(value) {
|
|
1473
|
-
return typeof value === "object" && value !== null ? value : null;
|
|
1474
|
-
}
|
|
1475
|
-
function readNonEmptyString(value) {
|
|
1476
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
// src/core/auth.ts
|
|
1480
|
-
function createAuthClient(config = {}) {
|
|
1481
|
-
let dataClient = config._data ?? null;
|
|
1482
|
-
const sessionStore = config.auth?.sessionStore ?? createMemorySessionStore();
|
|
1483
|
-
const getTransport = createTransportProvider(config);
|
|
1484
|
-
return {
|
|
1485
|
-
sendOtp: async (input, options) => {
|
|
1486
|
-
const transport = getTransport();
|
|
1487
|
-
if (!transport) {
|
|
1488
|
-
return stub("auth.sendOtp");
|
|
1489
|
-
}
|
|
1490
|
-
return await postBetterAuth(
|
|
1491
|
-
transport,
|
|
1492
|
-
"/email-otp/send-verification-otp",
|
|
1493
|
-
{ email: input.email, type: "sign-in" },
|
|
1494
|
-
"EMAIL_DELIVERY_FAILED",
|
|
1495
|
-
options?.signal
|
|
1496
|
-
);
|
|
1497
|
-
},
|
|
1498
|
-
verifyOtp: async (input, options) => {
|
|
1499
|
-
const transport = getTransport();
|
|
1500
|
-
if (!transport) {
|
|
1501
|
-
return stub("auth.verifyOtp");
|
|
1502
|
-
}
|
|
1503
|
-
const [signInError, signIn] = await postBetterAuth(
|
|
1504
|
-
transport,
|
|
1505
|
-
"/sign-in/email-otp",
|
|
1506
|
-
{ email: input.email, otp: input.otp },
|
|
1507
|
-
"NOT_AUTHENTICATED",
|
|
1508
|
-
options?.signal
|
|
1509
|
-
);
|
|
1510
|
-
if (signInError) return [signInError, null];
|
|
1511
|
-
if (!signIn?.token || !signIn.user?.id) {
|
|
1512
|
-
return [
|
|
1513
|
-
new CapxulError({
|
|
1514
|
-
code: "NOT_AUTHENTICATED",
|
|
1515
|
-
message: "BetterAuth did not return a usable session."
|
|
1516
|
-
}),
|
|
1517
|
-
null
|
|
1518
|
-
];
|
|
1519
|
-
}
|
|
1520
|
-
const [convexError, convexJwt] = await exchangeConvexToken(
|
|
1521
|
-
transport,
|
|
1522
|
-
config,
|
|
1523
|
-
signIn.token,
|
|
1524
|
-
options?.signal
|
|
1525
|
-
);
|
|
1526
|
-
if (convexError) return [convexError, null];
|
|
1527
|
-
const session = {
|
|
1528
|
-
authUserId: signIn.user.id,
|
|
1529
|
-
email: signIn.user.email,
|
|
1530
|
-
token: signIn.token,
|
|
1531
|
-
convexJwt,
|
|
1532
|
-
expiresAt: new Date(
|
|
1533
|
-
Date.now() + 30 * 24 * 60 * 60 * 1e3
|
|
1534
|
-
).toISOString()
|
|
1535
|
-
};
|
|
1536
|
-
sessionStore.set(session);
|
|
1537
|
-
if (!dataClient) {
|
|
1538
|
-
try {
|
|
1539
|
-
const convexUrl = transport.convexUrl;
|
|
1540
|
-
if (!convexUrl || !session.convexJwt) {
|
|
1541
|
-
return [
|
|
1542
|
-
new CapxulError({
|
|
1543
|
-
code: "NETWORK_ERROR",
|
|
1544
|
-
message: "Cannot create data client: missing convex URL or JWT."
|
|
1545
|
-
}),
|
|
1546
|
-
null
|
|
1547
|
-
];
|
|
1548
|
-
}
|
|
1549
|
-
dataClient = createDefaultDataClient(convexUrl, session.convexJwt);
|
|
1550
|
-
mutableConfig(config)._data = dataClient;
|
|
1551
|
-
} catch (cause) {
|
|
1552
|
-
return [
|
|
1553
|
-
new CapxulError({
|
|
1554
|
-
code: "NETWORK_ERROR",
|
|
1555
|
-
message: "Authenticated data client creation failed.",
|
|
1556
|
-
cause
|
|
1557
|
-
}),
|
|
1558
|
-
null
|
|
1559
|
-
];
|
|
1560
|
-
}
|
|
1561
|
-
} else {
|
|
1562
|
-
const injected = dataClient;
|
|
1563
|
-
if (typeof injected.refreshAuth === "function") {
|
|
1564
|
-
injected.refreshAuth();
|
|
1565
|
-
} else if (typeof injected.setAuth === "function" && session.convexJwt) {
|
|
1566
|
-
injected.setAuth(session.convexJwt);
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
transport.markAuthenticated({ dataClient });
|
|
1570
|
-
if (!dataClient) {
|
|
1571
|
-
return [
|
|
1572
|
-
new CapxulError({
|
|
1573
|
-
code: "NOT_AUTHENTICATED",
|
|
1574
|
-
message: "Auth bootstrap requires an authenticated Convex data client."
|
|
1575
|
-
}),
|
|
1576
|
-
null
|
|
1577
|
-
];
|
|
1578
|
-
}
|
|
1579
|
-
try {
|
|
1580
|
-
const resolution = await dataClient.mutation(
|
|
1581
|
-
api.authBootstrap.resolveAfterOtp,
|
|
1582
|
-
{
|
|
1583
|
-
email: session.email,
|
|
1584
|
-
sessionToken: session.token
|
|
1585
|
-
}
|
|
1586
|
-
);
|
|
1587
|
-
if (resolution.kind === "existing_member") {
|
|
1588
|
-
return [null, { ...resolution, session }];
|
|
1589
|
-
}
|
|
1590
|
-
return [null, { ...resolution, session }];
|
|
1591
|
-
} catch (cause) {
|
|
1592
|
-
return [fromConvexError(cause), null];
|
|
1593
|
-
}
|
|
1594
|
-
},
|
|
1595
|
-
completeBootstrap: async (input) => {
|
|
1596
|
-
const session = sessionStore.get();
|
|
1597
|
-
const data = dataClient ?? config._data;
|
|
1598
|
-
if (!session || !data) {
|
|
1599
|
-
return [
|
|
1600
|
-
new CapxulError({
|
|
1601
|
-
code: "INVALID_INPUT",
|
|
1602
|
-
message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
|
|
1603
|
-
}),
|
|
1604
|
-
null
|
|
1605
|
-
];
|
|
1606
|
-
}
|
|
1607
|
-
const signerAddress = config.signer?.address;
|
|
1608
|
-
if (!signerAddress) {
|
|
1609
|
-
return [
|
|
1610
|
-
new CapxulError({
|
|
1611
|
-
code: "INVALID_INPUT",
|
|
1612
|
-
message: "completeBootstrap requires a signer to be configured on the client."
|
|
1613
|
-
}),
|
|
1614
|
-
null
|
|
1615
|
-
];
|
|
1616
|
-
}
|
|
1617
|
-
try {
|
|
1618
|
-
const safeAddress = deriveSafeAddress2(signerAddress);
|
|
1619
|
-
if (!data.action) {
|
|
1620
|
-
return [
|
|
1621
|
-
new CapxulError({
|
|
1622
|
-
code: "INVALID_INPUT",
|
|
1623
|
-
message: "completeBootstrap requires a data client that can execute Convex actions."
|
|
1624
|
-
}),
|
|
1625
|
-
null
|
|
1626
|
-
];
|
|
1627
|
-
}
|
|
1628
|
-
const result = await data.action(api.authBootstrap.completeBootstrap, {
|
|
1629
|
-
bootstrapToken: input.bootstrapToken,
|
|
1630
|
-
sessionToken: session.token,
|
|
1631
|
-
username: input.username,
|
|
1632
|
-
displayName: input.displayName,
|
|
1633
|
-
countryCode: input.countryCode,
|
|
1634
|
-
signerProvider: {
|
|
1635
|
-
kind: "local-private-key",
|
|
1636
|
-
signerAddress,
|
|
1637
|
-
safeAddress
|
|
1638
|
-
}
|
|
1639
|
-
});
|
|
1640
|
-
return [null, { kind: "authenticated", session, ...result }];
|
|
1641
|
-
} catch (cause) {
|
|
1642
|
-
return [
|
|
1643
|
-
fromConvexError(cause),
|
|
1644
|
-
null
|
|
1645
|
-
];
|
|
1646
|
-
}
|
|
1647
|
-
},
|
|
1648
|
-
getSession: async () => [null, sessionStore.get()],
|
|
1649
|
-
signOut: async () => {
|
|
1650
|
-
sessionStore.clear();
|
|
1651
|
-
dataClient = null;
|
|
1652
|
-
mutableConfig(config)._data = void 0;
|
|
1653
|
-
const transport = getTransport();
|
|
1654
|
-
transport?.clearAuth();
|
|
1655
|
-
return [null, void 0];
|
|
1656
|
-
},
|
|
1657
|
-
serviceTokenMint: async () => stub("auth.serviceTokenMint"),
|
|
1658
|
-
getDataClient: () => dataClient
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
function createMemorySessionStore() {
|
|
1662
|
-
let current = null;
|
|
1663
|
-
return {
|
|
1664
|
-
get: () => current,
|
|
1665
|
-
set: (session) => {
|
|
1666
|
-
current = session;
|
|
1667
|
-
},
|
|
1668
|
-
clear: () => {
|
|
1669
|
-
current = null;
|
|
1670
|
-
}
|
|
1671
|
-
};
|
|
1672
|
-
}
|
|
1673
|
-
function createTransportProvider(config) {
|
|
1674
|
-
let cached = config._transport ?? null;
|
|
1675
|
-
return () => {
|
|
1676
|
-
if (cached) return cached;
|
|
1677
|
-
const baseUrl = config.auth?.baseUrl;
|
|
1678
|
-
if (baseUrl) {
|
|
1679
|
-
cached = makeHttpTransport({
|
|
1680
|
-
mode: "build-time-urls",
|
|
1681
|
-
authBaseUrl: betterAuthRoot(baseUrl),
|
|
1682
|
-
convexUrl: baseUrl,
|
|
1683
|
-
fetchImpl: config.fetch
|
|
1684
|
-
});
|
|
1685
|
-
return cached;
|
|
1686
|
-
}
|
|
1687
|
-
if (!config.publishableKey) return null;
|
|
1688
|
-
cached = makeHttpTransport({
|
|
1689
|
-
mode: "publishable-key",
|
|
1690
|
-
publishableKey: config.publishableKey,
|
|
1691
|
-
fetchImpl: config.fetch
|
|
1692
|
-
});
|
|
1693
|
-
return cached;
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
function betterAuthRoot(rawBaseUrl) {
|
|
1697
|
-
const trimmed = rawBaseUrl.replace(/\/+$/, "");
|
|
1698
|
-
return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
|
|
1699
|
-
}
|
|
1700
|
-
async function postBetterAuth(transport, path, body, code, signal) {
|
|
1701
|
-
try {
|
|
1702
|
-
const response = await transport.fetch(path, {
|
|
1703
|
-
method: "POST",
|
|
1704
|
-
headers: { "content-type": "application/json" },
|
|
1705
|
-
body: JSON.stringify(body),
|
|
1706
|
-
signal
|
|
1707
|
-
});
|
|
1708
|
-
const text = await response.text();
|
|
1709
|
-
if (!response.ok) {
|
|
1710
|
-
const parsedError = parseBetterAuthError(text);
|
|
1711
|
-
return [
|
|
1712
|
-
new CapxulError({
|
|
1713
|
-
code: parsedError.code ?? code,
|
|
1714
|
-
message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
|
|
1715
|
-
details: parsedError.details,
|
|
1716
|
-
retryable: parsedError.retryable
|
|
1717
|
-
}),
|
|
1718
|
-
null
|
|
1719
|
-
];
|
|
1720
|
-
}
|
|
1721
|
-
return [null, text ? JSON.parse(text) : void 0];
|
|
1722
|
-
} catch (cause) {
|
|
1723
|
-
if (cause instanceof CapxulError) {
|
|
1724
|
-
return [cause, null];
|
|
1725
|
-
}
|
|
1726
|
-
return [
|
|
1727
|
-
new CapxulError({
|
|
1728
|
-
code: "NETWORK_ERROR",
|
|
1729
|
-
message: `BetterAuth ${path} network failure.`,
|
|
1730
|
-
cause
|
|
1731
|
-
}),
|
|
1732
|
-
null
|
|
1733
|
-
];
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
function parseBetterAuthError(text) {
|
|
1737
|
-
if (!text.trim()) {
|
|
1738
|
-
return {};
|
|
1739
|
-
}
|
|
1740
|
-
try {
|
|
1741
|
-
const body = JSON.parse(text);
|
|
1742
|
-
if (!body || typeof body !== "object") {
|
|
1743
|
-
return {};
|
|
1744
|
-
}
|
|
1745
|
-
const record = body;
|
|
1746
|
-
const nested = record.error && typeof record.error === "object" ? record.error : record;
|
|
1747
|
-
const code = typeof nested.code === "string" ? nested.code : void 0;
|
|
1748
|
-
const message = typeof nested.message === "string" ? nested.message : void 0;
|
|
1749
|
-
const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
|
|
1750
|
-
const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
|
|
1751
|
-
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
1752
|
-
return {
|
|
1753
|
-
code: isCapxulErrorCode2(code) ? code : void 0,
|
|
1754
|
-
message,
|
|
1755
|
-
details,
|
|
1756
|
-
correlationId,
|
|
1757
|
-
retryable
|
|
1758
|
-
};
|
|
1759
|
-
} catch {
|
|
1760
|
-
return {};
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
function isCapxulErrorCode2(code) {
|
|
1764
|
-
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";
|
|
1765
|
-
}
|
|
1766
|
-
async function exchangeConvexToken(transport, config, token, signal) {
|
|
1767
|
-
const path = config.auth?.convexTokenUrl ?? "/convex/token";
|
|
1768
|
-
try {
|
|
1769
|
-
const response = await transport.fetch(path, {
|
|
1770
|
-
headers: { authorization: `Bearer ${token}` },
|
|
1771
|
-
signal
|
|
1772
|
-
});
|
|
1773
|
-
if (!response.ok) {
|
|
1774
|
-
return [
|
|
1775
|
-
new CapxulError({
|
|
1776
|
-
code: "NOT_AUTHENTICATED",
|
|
1777
|
-
message: `Convex token exchange failed with HTTP ${response.status}.`
|
|
1778
|
-
}),
|
|
1779
|
-
null
|
|
1780
|
-
];
|
|
1781
|
-
}
|
|
1782
|
-
const body = await response.json();
|
|
1783
|
-
if (typeof body.token !== "string") {
|
|
1784
|
-
return [
|
|
1785
|
-
new CapxulError({
|
|
1786
|
-
code: "NOT_AUTHENTICATED",
|
|
1787
|
-
message: "Convex token exchange returned no token."
|
|
1788
|
-
}),
|
|
1789
|
-
null
|
|
1790
|
-
];
|
|
1791
|
-
}
|
|
1792
|
-
return [null, body.token];
|
|
1793
|
-
} catch (cause) {
|
|
1794
|
-
if (cause instanceof CapxulError) {
|
|
1795
|
-
return [cause, null];
|
|
1796
|
-
}
|
|
1797
|
-
return [
|
|
1798
|
-
new CapxulError({
|
|
1799
|
-
code: "NETWORK_ERROR",
|
|
1800
|
-
message: "Convex token exchange network failure.",
|
|
1801
|
-
cause
|
|
1802
|
-
}),
|
|
1803
|
-
null
|
|
1804
|
-
];
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
function mutableConfig(config) {
|
|
1808
|
-
return config;
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
// src/core/auth-service.ts
|
|
1812
|
-
var AuthService = class {
|
|
1813
|
-
authClient;
|
|
1814
|
-
sessionStore;
|
|
1815
|
-
config;
|
|
1816
|
-
constructor(config = {}) {
|
|
1817
|
-
this.config = config;
|
|
1818
|
-
this.sessionStore = config.auth?.sessionStore ?? createMemorySessionStore2();
|
|
1819
|
-
this.authClient = createAuthClient({
|
|
1820
|
-
...config,
|
|
1821
|
-
auth: { ...config.auth, sessionStore: this.sessionStore }
|
|
1822
|
-
});
|
|
1823
|
-
}
|
|
1824
|
-
async sendOtp(email, options) {
|
|
1825
|
-
const [err] = await this.authClient.sendOtp({ email }, options);
|
|
1826
|
-
if (err) throw err;
|
|
1827
|
-
}
|
|
1828
|
-
async verifyOtp(email, otp, options) {
|
|
1829
|
-
const [err, result] = await this.authClient.verifyOtp(
|
|
1830
|
-
{ email, otp },
|
|
1831
|
-
options
|
|
1832
|
-
);
|
|
1833
|
-
if (err) throw err;
|
|
1834
|
-
return result;
|
|
1835
|
-
}
|
|
1836
|
-
async completeBootstrap(params, signer) {
|
|
1837
|
-
if (signer) {
|
|
1838
|
-
const tempClient = createAuthClient({
|
|
1839
|
-
...this.config,
|
|
1840
|
-
signer,
|
|
1841
|
-
auth: { ...this.config.auth, sessionStore: this.sessionStore }
|
|
1842
|
-
});
|
|
1843
|
-
const [err2, result2] = await tempClient.completeBootstrap(params);
|
|
1844
|
-
if (err2) throw err2;
|
|
1845
|
-
return result2;
|
|
1846
|
-
}
|
|
1847
|
-
const [err, result] = await this.authClient.completeBootstrap(params);
|
|
1848
|
-
if (err) throw err;
|
|
1849
|
-
return result;
|
|
1850
|
-
}
|
|
1851
|
-
/**
|
|
1852
|
-
* Clears the persisted session and, when a transport was pre-injected,
|
|
1853
|
-
* drops the cached auth header.
|
|
1854
|
-
*
|
|
1855
|
-
* **Transport safety note:** `clearAuth()` is only invoked when
|
|
1856
|
-
* `config._transport` was supplied at construction (e.g. by the React
|
|
1857
|
-
* provider). If `AuthService` is instantiated directly in a Node/CLI
|
|
1858
|
-
* context without an injected transport, the transport-side auth cache
|
|
1859
|
-
* is the caller's responsibility.
|
|
1860
|
-
*/
|
|
1861
|
-
async signOut() {
|
|
1862
|
-
if (!this.config._transport) {
|
|
1863
|
-
const [err] = await this.authClient.signOut();
|
|
1864
|
-
if (err) throw err;
|
|
1865
|
-
mutableConfig2(this.config)._data = void 0;
|
|
1866
|
-
return;
|
|
1867
|
-
}
|
|
1868
|
-
this.sessionStore.clear();
|
|
1869
|
-
this.config._transport.clearAuth();
|
|
1870
|
-
}
|
|
1871
|
-
async getSession() {
|
|
1872
|
-
const [err, session] = await this.authClient.getSession();
|
|
1873
|
-
if (err) throw err;
|
|
1874
|
-
return session;
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
function mutableConfig2(config) {
|
|
1878
|
-
return config;
|
|
1879
|
-
}
|
|
1880
|
-
function createMemorySessionStore2() {
|
|
1881
|
-
let current = null;
|
|
1882
|
-
return {
|
|
1883
|
-
get: () => current,
|
|
1884
|
-
set: (session) => {
|
|
1885
|
-
current = session;
|
|
1886
|
-
},
|
|
1887
|
-
clear: () => {
|
|
1888
|
-
current = null;
|
|
1889
|
-
}
|
|
1890
|
-
};
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
// src/core/documents.ts
|
|
1894
|
-
function requireInvoiceHash(row) {
|
|
1895
|
-
if (!row.invoiceHash) {
|
|
1896
|
-
throw new CapxulError({
|
|
1897
|
-
code: "INVALID_INPUT",
|
|
1898
|
-
message: `invoice document ${row.documentId ?? row._id} is missing its canonical invoiceHash`
|
|
1899
|
-
});
|
|
1900
|
-
}
|
|
1901
|
-
return row.invoiceHash;
|
|
1902
|
-
}
|
|
1903
|
-
function mapInvoiceRow(row) {
|
|
1904
|
-
const status = row.status === "cancelled" ? "canceled" : row.status === "pending" ? "open" : row.status === "overdue" ? "expired" : row.status;
|
|
1905
|
-
return {
|
|
1906
|
-
object: "document",
|
|
1907
|
-
id: row.documentId ?? row._id,
|
|
1908
|
-
type: "invoice",
|
|
1909
|
-
owner: {
|
|
1910
|
-
kind: row.scope === "org" ? "organization" : "account",
|
|
1911
|
-
id: row.orgId ?? row.payeeEmail ?? row.payeeLabel
|
|
1912
|
-
},
|
|
1913
|
-
recipient: { email: row.payerEmail },
|
|
1914
|
-
amount: {
|
|
1915
|
-
value: row.amount,
|
|
1916
|
-
currency: row.currency
|
|
1917
|
-
},
|
|
1918
|
-
reference: row.note,
|
|
1919
|
-
lineItems: row.items,
|
|
1920
|
-
invoiceHash: requireInvoiceHash(row),
|
|
1921
|
-
dueAt: row.dueDate,
|
|
1922
|
-
status,
|
|
1923
|
-
createdAt: new Date(row.createdAt).toISOString()
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
function mapDocumentError(cause) {
|
|
1927
|
-
return fromConvexError(cause);
|
|
1928
|
-
}
|
|
1929
|
-
function createDocumentsClient(config = {}) {
|
|
1930
|
-
return {
|
|
1931
|
-
create: async (input) => {
|
|
1932
|
-
if (!config._data) return stub("documents.create");
|
|
1933
|
-
if (input.type !== "invoice") {
|
|
1934
|
-
return [
|
|
1935
|
-
new CapxulError({
|
|
1936
|
-
code: "INVALID_INPUT",
|
|
1937
|
-
message: "documents.create currently supports personal invoice documents only."
|
|
1938
|
-
}),
|
|
1939
|
-
null
|
|
1940
|
-
];
|
|
1941
|
-
}
|
|
1942
|
-
if (!("email" in input.recipient)) {
|
|
1943
|
-
return [
|
|
1944
|
-
new CapxulError({
|
|
1945
|
-
code: "INVALID_INPUT",
|
|
1946
|
-
message: "personal invoice documents currently require an email recipient."
|
|
1947
|
-
}),
|
|
1948
|
-
null
|
|
1949
|
-
];
|
|
1950
|
-
}
|
|
1951
|
-
try {
|
|
1952
|
-
const created = await config._data.mutation(
|
|
1953
|
-
api.paymentRecords.mutations.createInvoice,
|
|
1954
|
-
{
|
|
1955
|
-
scope: "personal",
|
|
1956
|
-
payerEmail: input.recipient.email,
|
|
1957
|
-
amount: input.amount.value,
|
|
1958
|
-
currency: input.amount.currency,
|
|
1959
|
-
dueDate: input.dueAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1960
|
-
note: input.reference,
|
|
1961
|
-
items: input.lineItems
|
|
1962
|
-
}
|
|
1963
|
-
);
|
|
1964
|
-
const row = await config._data.query(
|
|
1965
|
-
api.paymentRecords.queries.getInvoiceByDocumentId,
|
|
1966
|
-
{ documentId: created.documentId }
|
|
1967
|
-
);
|
|
1968
|
-
if (!row) throw new Error("created invoice was not readable");
|
|
1969
|
-
return [null, mapInvoiceRow(row)];
|
|
1970
|
-
} catch (cause) {
|
|
1971
|
-
return [mapDocumentError(cause), null];
|
|
1972
|
-
}
|
|
1973
|
-
},
|
|
1974
|
-
retrieve: async (documentId) => {
|
|
1975
|
-
if (!config._data)
|
|
1976
|
-
return stub("documents.retrieve");
|
|
1977
|
-
try {
|
|
1978
|
-
const row = await config._data.query(
|
|
1979
|
-
api.paymentRecords.queries.getInvoiceByDocumentId,
|
|
1980
|
-
{ documentId }
|
|
1981
|
-
);
|
|
1982
|
-
if (!row) {
|
|
1983
|
-
return [
|
|
1984
|
-
new CapxulError({
|
|
1985
|
-
code: "NOT_FOUND",
|
|
1986
|
-
message: `document ${documentId} not found`
|
|
1987
|
-
}),
|
|
1988
|
-
null
|
|
1989
|
-
];
|
|
1990
|
-
}
|
|
1991
|
-
return [null, mapInvoiceRow(row)];
|
|
1992
|
-
} catch (cause) {
|
|
1993
|
-
return [mapDocumentError(cause), null];
|
|
1994
|
-
}
|
|
1995
|
-
},
|
|
1996
|
-
list: async (input = {}) => {
|
|
1997
|
-
if (!config._data)
|
|
1998
|
-
return stub("documents.list");
|
|
1999
|
-
try {
|
|
2000
|
-
if (input.type && input.type !== "invoice") {
|
|
2001
|
-
return [null, { object: "list", data: [], page: { hasMore: false } }];
|
|
2002
|
-
}
|
|
2003
|
-
const rows = await config._data.query(
|
|
2004
|
-
api.paymentRecords.queries.listMyInvoices,
|
|
2005
|
-
{
|
|
2006
|
-
limit: input.limit,
|
|
2007
|
-
cursor: input.cursor
|
|
2008
|
-
}
|
|
2009
|
-
);
|
|
2010
|
-
const page = rows;
|
|
2011
|
-
const data = page.data.map((row) => mapInvoiceRow(row));
|
|
2012
|
-
return [null, { object: "list", data, page: page.page }];
|
|
2013
|
-
} catch (cause) {
|
|
2014
|
-
return [mapDocumentError(cause), null];
|
|
2015
|
-
}
|
|
2016
|
-
},
|
|
2017
|
-
cancel: async (documentId) => {
|
|
2018
|
-
if (!config._data) return stub("documents.cancel");
|
|
2019
|
-
try {
|
|
2020
|
-
const row = await config._data.mutation(
|
|
2021
|
-
api.paymentRecords.mutations.cancelInvoice,
|
|
2022
|
-
{ documentId }
|
|
2023
|
-
);
|
|
2024
|
-
return [null, mapInvoiceRow(row)];
|
|
2025
|
-
} catch (cause) {
|
|
2026
|
-
return [mapDocumentError(cause), null];
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
};
|
|
2030
|
-
}
|
|
2031
|
-
function createOrgDocumentsClient(_config = {}) {
|
|
2032
|
-
return {
|
|
2033
|
-
create: async () => stub("organizations.documents.create"),
|
|
2034
|
-
retrieve: async () => stub("organizations.documents.retrieve"),
|
|
2035
|
-
list: async () => stub("organizations.documents.list"),
|
|
2036
|
-
cancel: async () => stub("organizations.documents.cancel")
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
// src/core/me.ts
|
|
2041
|
-
function createMeClient(config = {}) {
|
|
2042
|
-
return {
|
|
2043
|
-
get: async () => {
|
|
2044
|
-
if (!config._data) {
|
|
2045
|
-
return stub("me.get");
|
|
2046
|
-
}
|
|
2047
|
-
try {
|
|
2048
|
-
const account = await config._data.query(
|
|
2049
|
-
api.openfort.queries.getMyAccount,
|
|
2050
|
-
{}
|
|
2051
|
-
);
|
|
2052
|
-
return [null, account];
|
|
2053
|
-
} catch (cause) {
|
|
2054
|
-
return [fromConvexError(cause), null];
|
|
2055
|
-
}
|
|
2056
|
-
},
|
|
2057
|
-
update: async (input) => {
|
|
2058
|
-
if (!config._data) {
|
|
2059
|
-
return stub("me.update");
|
|
2060
|
-
}
|
|
2061
|
-
if (input.countryCode !== void 0) {
|
|
2062
|
-
return [
|
|
2063
|
-
new CapxulError({
|
|
2064
|
-
code: "INVALID_INPUT",
|
|
2065
|
-
message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
|
|
2066
|
-
details: { field: "countryCode" }
|
|
2067
|
-
}),
|
|
2068
|
-
null
|
|
2069
|
-
];
|
|
2070
|
-
}
|
|
2071
|
-
try {
|
|
2072
|
-
await config._data.mutation(api.openfort.mutations.updateProfile, {
|
|
2073
|
-
displayName: input.name,
|
|
2074
|
-
username: input.username
|
|
2075
|
-
});
|
|
2076
|
-
const account = await config._data.query(
|
|
2077
|
-
api.openfort.queries.getMyAccount,
|
|
2078
|
-
{}
|
|
2079
|
-
);
|
|
2080
|
-
return [null, account];
|
|
2081
|
-
} catch (cause) {
|
|
2082
|
-
return [fromConvexError(cause), null];
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
// src/core/operations.ts
|
|
2089
|
-
function createOperationsClient(config = {}) {
|
|
2090
|
-
const retrieve = async (operationId) => {
|
|
2091
|
-
if (!config._data) {
|
|
2092
|
-
return stub("operations.retrieve");
|
|
2093
|
-
}
|
|
2094
|
-
try {
|
|
2095
|
-
const operation = await config._data.query(api.operations.queries.retrieve, {
|
|
2096
|
-
operationId
|
|
2097
|
-
});
|
|
2098
|
-
if (!operation) {
|
|
2099
|
-
return [new CapxulError({
|
|
2100
|
-
code: "NOT_FOUND",
|
|
2101
|
-
message: `operation ${operationId} not found`
|
|
2102
|
-
}), null];
|
|
2103
|
-
}
|
|
2104
|
-
return [null, operation];
|
|
2105
|
-
} catch (cause) {
|
|
2106
|
-
return [fromConvexError(cause), null];
|
|
2107
|
-
}
|
|
2108
|
-
};
|
|
2109
|
-
return {
|
|
2110
|
-
retrieve,
|
|
2111
|
-
wait: async (operationId, input = {}) => {
|
|
2112
|
-
if (!config._data) {
|
|
2113
|
-
return stub("operations.wait");
|
|
2114
|
-
}
|
|
2115
|
-
const until = new Set(
|
|
2116
|
-
input.until ?? ["succeeded", "failed", "canceled", "indexed"]
|
|
2117
|
-
);
|
|
2118
|
-
const timeoutMs = (input.timeoutSeconds ?? 60) * 1e3;
|
|
2119
|
-
const pollIntervalMs = input.pollIntervalMs ?? 1e3;
|
|
2120
|
-
const deadline = Date.now() + timeoutMs;
|
|
2121
|
-
while (Date.now() <= deadline) {
|
|
2122
|
-
const [error, operation] = await retrieve(operationId);
|
|
2123
|
-
if (error) {
|
|
2124
|
-
return [error, null];
|
|
2125
|
-
}
|
|
2126
|
-
if (until.has(operation.status)) {
|
|
2127
|
-
return [null, operation];
|
|
2128
|
-
}
|
|
2129
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2130
|
-
}
|
|
2131
|
-
return [new CapxulError({
|
|
2132
|
-
code: "OPERATION_TIMEOUT",
|
|
2133
|
-
message: `operation ${operationId} did not reach a terminal state before the timeout`,
|
|
2134
|
-
details: {
|
|
2135
|
-
operationId,
|
|
2136
|
-
timeoutSeconds: input.timeoutSeconds ?? 60,
|
|
2137
|
-
pollIntervalMs
|
|
2138
|
-
}
|
|
2139
|
-
}), null];
|
|
2140
|
-
}
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
function toTokenUnits(value, decimals = 6) {
|
|
2144
|
-
return viem.parseUnits(value, decimals);
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
// src/core/token-registry.ts
|
|
2148
|
-
function resolvePaymentToken(currency) {
|
|
2149
|
-
const normalized = currency.trim().toUpperCase();
|
|
2150
|
-
if (normalized === "USD" || normalized === "USDC") {
|
|
2151
|
-
return {
|
|
2152
|
-
address: TEST_USDC_ADDRESS.toLowerCase(),
|
|
2153
|
-
decimals: 6,
|
|
2154
|
-
symbol: "USDC"
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
throw new CapxulError({
|
|
2158
|
-
code: "NOT_IMPLEMENTED",
|
|
2159
|
-
message: `Currency ${currency} is not yet supported by the token registry.`,
|
|
2160
|
-
details: { currency: normalized }
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
function createCapxulBundler(config) {
|
|
2164
|
-
const paymaster = accountAbstraction.createPaymasterClient({
|
|
2165
|
-
transport: viem.http(config.rpcUrl)
|
|
2166
|
-
});
|
|
2167
|
-
return accountAbstraction.createBundlerClient({
|
|
2168
|
-
chain: chains.baseSepolia,
|
|
2169
|
-
transport: viem.http(config.rpcUrl),
|
|
2170
|
-
paymaster,
|
|
2171
|
-
paymasterContext: { policyId: config.gasPolicyId }
|
|
2172
|
-
});
|
|
2173
|
-
}
|
|
2174
|
-
var CAPXUL_PAYMENTS_SEND_ABI = [
|
|
2175
|
-
{
|
|
2176
|
-
name: "send",
|
|
2177
|
-
type: "function",
|
|
2178
|
-
stateMutability: "nonpayable",
|
|
2179
|
-
inputs: [
|
|
2180
|
-
{ name: "token", type: "address" },
|
|
2181
|
-
{ name: "recipient", type: "address" },
|
|
2182
|
-
{ name: "amount", type: "uint256" },
|
|
2183
|
-
{ name: "documentHash", type: "bytes32" },
|
|
2184
|
-
{ name: "paymentType", type: "uint8" }
|
|
2185
|
-
],
|
|
2186
|
-
outputs: []
|
|
2187
|
-
}
|
|
2188
|
-
];
|
|
2189
|
-
var ERC20_APPROVE_ABI = [
|
|
2190
|
-
{
|
|
2191
|
-
name: "approve",
|
|
2192
|
-
type: "function",
|
|
2193
|
-
stateMutability: "nonpayable",
|
|
2194
|
-
inputs: [
|
|
2195
|
-
{ name: "spender", type: "address" },
|
|
2196
|
-
{ name: "amount", type: "uint256" }
|
|
2197
|
-
],
|
|
2198
|
-
outputs: [{ name: "", type: "bool" }]
|
|
2199
|
-
}
|
|
2200
|
-
];
|
|
2201
|
-
function encodeOwnerTransferCalls(params) {
|
|
2202
|
-
const documentHash = params.documentHash ?? "0x" + "0".repeat(64);
|
|
2203
|
-
const paymentType = params.paymentType ?? 0;
|
|
2204
|
-
const approve = viem.encodeFunctionData({
|
|
2205
|
-
abi: ERC20_APPROVE_ABI,
|
|
2206
|
-
functionName: "approve",
|
|
2207
|
-
args: [CAPXUL_PAYMENTS_ADDRESS, params.amount]
|
|
2208
|
-
});
|
|
2209
|
-
const send = viem.encodeFunctionData({
|
|
2210
|
-
abi: CAPXUL_PAYMENTS_SEND_ABI,
|
|
2211
|
-
functionName: "send",
|
|
2212
|
-
args: [
|
|
2213
|
-
params.tokenAddress,
|
|
2214
|
-
params.recipientAddress,
|
|
2215
|
-
params.amount,
|
|
2216
|
-
documentHash,
|
|
2217
|
-
paymentType
|
|
2218
|
-
]
|
|
2219
|
-
});
|
|
2220
|
-
return [
|
|
2221
|
-
{
|
|
2222
|
-
to: params.tokenAddress.toLowerCase(),
|
|
2223
|
-
data: approve,
|
|
2224
|
-
value: 0n
|
|
2225
|
-
},
|
|
2226
|
-
{
|
|
2227
|
-
to: CAPXUL_PAYMENTS_ADDRESS,
|
|
2228
|
-
data: send,
|
|
2229
|
-
value: 0n
|
|
2230
|
-
}
|
|
2231
|
-
];
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
// src/internal/safe/operations.ts
|
|
2235
|
-
var USER_OP_RECEIPT_TIMEOUT_MS = 12e4;
|
|
2236
|
-
async function transferAsOwner(config, params) {
|
|
2237
|
-
try {
|
|
2238
|
-
const safeAccount = await buildSafeAccount(config.signer, config.signing);
|
|
2239
|
-
const bundler = createCapxulBundler(config.signing);
|
|
2240
|
-
const calls = encodeOwnerTransferCalls(params);
|
|
2241
|
-
const userOpHash = await bundler.sendUserOperation({
|
|
2242
|
-
account: safeAccount,
|
|
2243
|
-
calls
|
|
2244
|
-
});
|
|
2245
|
-
const receipt = await bundler.waitForUserOperationReceipt({
|
|
2246
|
-
hash: userOpHash,
|
|
2247
|
-
timeout: USER_OP_RECEIPT_TIMEOUT_MS
|
|
2248
|
-
});
|
|
2249
|
-
return {
|
|
2250
|
-
txHash: receipt.receipt.transactionHash,
|
|
2251
|
-
userOpHash,
|
|
2252
|
-
blockNumber: Number(receipt.receipt.blockNumber),
|
|
2253
|
-
success: receipt.success,
|
|
2254
|
-
logs: receipt.receipt.logs
|
|
2255
|
-
};
|
|
2256
|
-
} catch (cause) {
|
|
2257
|
-
throw new CapxulError({
|
|
2258
|
-
code: "NETWORK_ERROR",
|
|
2259
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
2260
|
-
cause
|
|
2261
|
-
});
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
// src/core/payments.ts
|
|
2266
|
-
function createPaymentsClient(config = {}) {
|
|
2267
|
-
return {
|
|
2268
|
-
create: async (input) => {
|
|
2269
|
-
if (!config._data || !config.signer || !config.signing) {
|
|
2270
|
-
return stub("payments.create");
|
|
2271
|
-
}
|
|
2272
|
-
let created = null;
|
|
2273
|
-
let submitted = null;
|
|
2274
|
-
try {
|
|
2275
|
-
created = await config._data.mutation(api.payments.mutations.create, {
|
|
2276
|
-
to: input.to,
|
|
2277
|
-
amount: input.amount,
|
|
2278
|
-
reference: input.reference,
|
|
2279
|
-
idempotencyKey: input.idempotencyKey,
|
|
2280
|
-
source: input.source
|
|
2281
|
-
});
|
|
2282
|
-
if (!created) {
|
|
2283
|
-
return [
|
|
2284
|
-
new CapxulError({
|
|
2285
|
-
code: "NETWORK_ERROR",
|
|
2286
|
-
message: "payments.create returned no payment resource"
|
|
2287
|
-
}),
|
|
2288
|
-
null
|
|
2289
|
-
];
|
|
2290
|
-
}
|
|
2291
|
-
if (created.status !== "processing" || created.operation.status !== "processing") {
|
|
2292
|
-
return [null, created];
|
|
2293
|
-
}
|
|
2294
|
-
const currentSigner = await config._data.query(
|
|
2295
|
-
api.safe.queries.getMySignerAddress,
|
|
2296
|
-
{}
|
|
2297
|
-
);
|
|
2298
|
-
if (!currentSigner?.address) {
|
|
2299
|
-
throw new CapxulError({
|
|
2300
|
-
code: "PERMISSION_DENIED",
|
|
2301
|
-
message: "No signer is registered for the authenticated account.",
|
|
2302
|
-
details: { paymentId: created.id }
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
|
|
2306
|
-
throw new CapxulError({
|
|
2307
|
-
code: "PERMISSION_DENIED",
|
|
2308
|
-
message: "Configured signer does not match the authenticated account signer.",
|
|
2309
|
-
details: {
|
|
2310
|
-
paymentId: created.id,
|
|
2311
|
-
expectedSignerAddress: currentSigner.address,
|
|
2312
|
-
actualSignerAddress: config.signer.address
|
|
2313
|
-
}
|
|
2314
|
-
});
|
|
2315
|
-
}
|
|
2316
|
-
const submission = await config._data.query(
|
|
2317
|
-
api.payments.queries.prepareSubmission,
|
|
2318
|
-
{
|
|
2319
|
-
paymentId: created.id
|
|
2320
|
-
}
|
|
2321
|
-
);
|
|
2322
|
-
if (!submission?.recipientAddress) {
|
|
2323
|
-
throw new CapxulError({
|
|
2324
|
-
code: "NETWORK_ERROR",
|
|
2325
|
-
message: "payments.prepareSubmission returned no recipient address.",
|
|
2326
|
-
details: { paymentId: created.id }
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
const token = resolvePaymentToken(submission.amount.currency);
|
|
2330
|
-
const transfer = await transferAsOwner(
|
|
2331
|
-
{
|
|
2332
|
-
signer: config.signer,
|
|
2333
|
-
signing: config.signing
|
|
2334
|
-
},
|
|
2335
|
-
{
|
|
2336
|
-
tokenAddress: token.address,
|
|
2337
|
-
recipientAddress: submission.recipientAddress,
|
|
2338
|
-
amount: toTokenUnits(submission.amount.value, token.decimals)
|
|
2339
|
-
}
|
|
2340
|
-
);
|
|
2341
|
-
if (!transfer.success) {
|
|
2342
|
-
throw new CapxulError({
|
|
2343
|
-
code: "NETWORK_ERROR",
|
|
2344
|
-
message: "Bundler submission did not succeed.",
|
|
2345
|
-
details: {
|
|
2346
|
-
paymentId: created.id,
|
|
2347
|
-
txHash: transfer.txHash,
|
|
2348
|
-
userOpHash: transfer.userOpHash
|
|
2349
|
-
}
|
|
2350
|
-
});
|
|
2351
|
-
}
|
|
2352
|
-
submitted = {
|
|
2353
|
-
txHash: transfer.txHash,
|
|
2354
|
-
userOpHash: transfer.userOpHash
|
|
2355
|
-
};
|
|
2356
|
-
await config._data.mutation(api.payments.mutations.recordSubmitted, {
|
|
2357
|
-
paymentId: created.id,
|
|
2358
|
-
txHash: transfer.txHash,
|
|
2359
|
-
userOpHash: transfer.userOpHash,
|
|
2360
|
-
source: "sdk"
|
|
2361
|
-
});
|
|
2362
|
-
return [null, created];
|
|
2363
|
-
} catch (cause) {
|
|
2364
|
-
const error = mapCreateError(fromConvexError(cause));
|
|
2365
|
-
if (created?.id && created.status === "processing" && !submitted) {
|
|
2366
|
-
await bestEffortMarkFailed({ _data: config._data }, created.id, error);
|
|
2367
|
-
}
|
|
2368
|
-
if (submitted && created?.id) {
|
|
2369
|
-
return [
|
|
2370
|
-
new CapxulError({
|
|
2371
|
-
code: "NETWORK_ERROR",
|
|
2372
|
-
message: "Payment was submitted on-chain, but backend submission tracking failed.",
|
|
2373
|
-
cause,
|
|
2374
|
-
details: {
|
|
2375
|
-
paymentId: created.id,
|
|
2376
|
-
txHash: submitted.txHash,
|
|
2377
|
-
userOpHash: submitted.userOpHash
|
|
2378
|
-
}
|
|
2379
|
-
}),
|
|
2380
|
-
null
|
|
2381
|
-
];
|
|
2382
|
-
}
|
|
2383
|
-
return [error, null];
|
|
2384
|
-
}
|
|
2385
|
-
},
|
|
2386
|
-
retrieve: async (paymentId) => {
|
|
2387
|
-
if (!config._data) {
|
|
2388
|
-
return stub("payments.retrieve");
|
|
2389
|
-
}
|
|
2390
|
-
try {
|
|
2391
|
-
const payment = await config._data.query(
|
|
2392
|
-
api.payments.queries.retrieve,
|
|
2393
|
-
{
|
|
2394
|
-
paymentId
|
|
2395
|
-
}
|
|
2396
|
-
);
|
|
2397
|
-
if (!payment) {
|
|
2398
|
-
return [
|
|
2399
|
-
new CapxulError({
|
|
2400
|
-
code: "NOT_FOUND",
|
|
2401
|
-
message: `payment ${paymentId} not found`
|
|
2402
|
-
}),
|
|
2403
|
-
null
|
|
2404
|
-
];
|
|
2405
|
-
}
|
|
2406
|
-
return [null, payment];
|
|
2407
|
-
} catch (cause) {
|
|
2408
|
-
return [fromConvexError(cause), null];
|
|
2409
|
-
}
|
|
2410
|
-
},
|
|
2411
|
-
list: async (input) => {
|
|
2412
|
-
if (!config._data) {
|
|
2413
|
-
return stub("payments.list");
|
|
2414
|
-
}
|
|
2415
|
-
try {
|
|
2416
|
-
const page = await config._data.query(api.payments.queries.list, {
|
|
2417
|
-
limit: input?.limit,
|
|
2418
|
-
cursor: input?.cursor
|
|
2419
|
-
});
|
|
2420
|
-
return [null, page];
|
|
2421
|
-
} catch (cause) {
|
|
2422
|
-
return [fromConvexError(cause), null];
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
function createOrgPaymentsClient() {
|
|
2428
|
-
return {
|
|
2429
|
-
create: async () => stub(
|
|
2430
|
-
"organizations.payments.create"
|
|
2431
|
-
),
|
|
2432
|
-
retrieve: async () => stub("organizations.payments.retrieve"),
|
|
2433
|
-
list: async () => stub("organizations.payments.list")
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
async function bestEffortMarkFailed(config, paymentId, error) {
|
|
2437
|
-
try {
|
|
2438
|
-
await config._data.mutation(api.payments.mutations.markFailed, {
|
|
2439
|
-
paymentId,
|
|
2440
|
-
errorCode: error.code,
|
|
2441
|
-
errorMessage: error.message,
|
|
2442
|
-
source: "sdk"
|
|
2443
|
-
});
|
|
2444
|
-
} catch {
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
function mapCreateError(error) {
|
|
2448
|
-
switch (error.code) {
|
|
2449
|
-
case "NOT_AUTHENTICATED":
|
|
2450
|
-
case "PERMISSION_DENIED":
|
|
2451
|
-
case "INVALID_INPUT":
|
|
2452
|
-
case "INVALID_RECIPIENT":
|
|
2453
|
-
case "INSUFFICIENT_BALANCE":
|
|
2454
|
-
case "IDEMPOTENCY_CONFLICT":
|
|
2455
|
-
case "RATE_LIMITED":
|
|
2456
|
-
case "NETWORK_ERROR":
|
|
2457
|
-
return error;
|
|
2458
|
-
default:
|
|
2459
|
-
return new CapxulError({
|
|
2460
|
-
code: "NETWORK_ERROR",
|
|
2461
|
-
message: error.message,
|
|
2462
|
-
cause: error,
|
|
2463
|
-
details: error.details,
|
|
2464
|
-
operationId: error.operationId,
|
|
2465
|
-
correlationId: error.correlationId,
|
|
2466
|
-
retryable: error.retryable
|
|
2467
|
-
});
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
// src/core/transfers.ts
|
|
2472
|
-
function createTransfersClient() {
|
|
2473
|
-
return {
|
|
2474
|
-
create: async () => stub("transfers.create"),
|
|
2475
|
-
retrieve: async () => stub("transfers.retrieve"),
|
|
2476
|
-
list: async () => stub("transfers.list"),
|
|
2477
|
-
confirm: async () => stub("transfers.confirm"),
|
|
2478
|
-
cancel: async () => stub("transfers.cancel")
|
|
2479
|
-
};
|
|
2480
|
-
}
|
|
2481
|
-
function createOrgTransfersClient() {
|
|
2482
|
-
return {
|
|
2483
|
-
create: async () => stub(
|
|
2484
|
-
"organizations.transfers.create"
|
|
2485
|
-
),
|
|
2486
|
-
retrieve: async () => stub("organizations.transfers.retrieve"),
|
|
2487
|
-
list: async () => stub("organizations.transfers.list"),
|
|
2488
|
-
confirm: async () => stub("organizations.transfers.confirm"),
|
|
2489
|
-
cancel: async () => stub("organizations.transfers.cancel")
|
|
2490
|
-
};
|
|
2491
|
-
}
|
|
2492
|
-
function createWithdrawalsClient(config = {}) {
|
|
2493
|
-
return {
|
|
2494
|
-
create: async (input) => {
|
|
2495
|
-
if (!config._data) {
|
|
2496
|
-
return stub("withdrawals.create");
|
|
2497
|
-
}
|
|
2498
|
-
const [createErr, createdRaw] = await tryCatch(
|
|
2499
|
-
config._data.mutation(api.withdrawals.mutations.create, {
|
|
2500
|
-
amount: input.amount,
|
|
2501
|
-
destination: {
|
|
2502
|
-
externalAccountId: input.destination.externalAccountId
|
|
2503
|
-
},
|
|
2504
|
-
source: input.source,
|
|
2505
|
-
reference: input.reference,
|
|
2506
|
-
idempotencyKey: input.idempotencyKey
|
|
2507
|
-
})
|
|
2508
|
-
);
|
|
2509
|
-
if (createErr) {
|
|
2510
|
-
return [mapCreateError2(fromConvexError(createErr)), null];
|
|
2511
|
-
}
|
|
2512
|
-
const created = createdRaw;
|
|
2513
|
-
if (!created) {
|
|
2514
|
-
return [
|
|
2515
|
-
new CapxulError({
|
|
2516
|
-
code: "NETWORK_ERROR",
|
|
2517
|
-
message: "withdrawals.create returned no withdrawal resource"
|
|
2518
|
-
}),
|
|
2519
|
-
null
|
|
2520
|
-
];
|
|
2521
|
-
}
|
|
2522
|
-
if (created.status !== "processing" || created.operation.status !== "processing") {
|
|
2523
|
-
return [null, created];
|
|
2524
|
-
}
|
|
2525
|
-
if (!config.signer || !config.signing) {
|
|
2526
|
-
return [null, created];
|
|
2527
|
-
}
|
|
2528
|
-
const [signerErr, currentSigner] = await tryCatch(
|
|
2529
|
-
config._data.query(api.safe.queries.getMySignerAddress, {})
|
|
2530
|
-
);
|
|
2531
|
-
if (signerErr) {
|
|
2532
|
-
return await handleSubmissionFailure(
|
|
2533
|
-
{ _data: config._data },
|
|
2534
|
-
created.id,
|
|
2535
|
-
mapCreateError2(fromConvexError(signerErr))
|
|
2536
|
-
);
|
|
2537
|
-
}
|
|
2538
|
-
if (!currentSigner?.address) {
|
|
2539
|
-
return await handleSubmissionFailure(
|
|
2540
|
-
{ _data: config._data },
|
|
2541
|
-
created.id,
|
|
2542
|
-
new CapxulError({
|
|
2543
|
-
code: "PERMISSION_DENIED",
|
|
2544
|
-
message: "No signer is registered for the authenticated account.",
|
|
2545
|
-
details: { withdrawalId: created.id }
|
|
2546
|
-
})
|
|
2547
|
-
);
|
|
2548
|
-
}
|
|
2549
|
-
if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
|
|
2550
|
-
return await handleSubmissionFailure(
|
|
2551
|
-
{ _data: config._data },
|
|
2552
|
-
created.id,
|
|
2553
|
-
new CapxulError({
|
|
2554
|
-
code: "PERMISSION_DENIED",
|
|
2555
|
-
message: "Configured signer does not match the authenticated account signer.",
|
|
2556
|
-
details: {
|
|
2557
|
-
withdrawalId: created.id,
|
|
2558
|
-
expectedSignerAddress: currentSigner.address,
|
|
2559
|
-
actualSignerAddress: config.signer.address
|
|
2560
|
-
}
|
|
2561
|
-
})
|
|
2562
|
-
);
|
|
2563
|
-
}
|
|
2564
|
-
const [prepErr, submission] = await tryCatch(
|
|
2565
|
-
config._data.query(api.withdrawals.queries.prepareSubmission, {
|
|
2566
|
-
withdrawalId: created.id
|
|
2567
|
-
})
|
|
2568
|
-
);
|
|
2569
|
-
if (prepErr) {
|
|
2570
|
-
return await handleSubmissionFailure(
|
|
2571
|
-
{ _data: config._data },
|
|
2572
|
-
created.id,
|
|
2573
|
-
mapCreateError2(fromConvexError(prepErr))
|
|
2574
|
-
);
|
|
2575
|
-
}
|
|
2576
|
-
const destinationAddress = submission?.destinationAddress;
|
|
2577
|
-
if (!submission || !destinationAddress) {
|
|
2578
|
-
return await handleSubmissionFailure(
|
|
2579
|
-
{ _data: config._data },
|
|
2580
|
-
created.id,
|
|
2581
|
-
new CapxulError({
|
|
2582
|
-
code: "NETWORK_ERROR",
|
|
2583
|
-
message: "withdrawals.prepareSubmission returned no destination.",
|
|
2584
|
-
details: { withdrawalId: created.id }
|
|
2585
|
-
})
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
const token = resolvePaymentToken(submission.amount.currency);
|
|
2589
|
-
const [transferErr, transferOk] = await tryCatch(
|
|
2590
|
-
transferAsOwner(
|
|
2591
|
-
{
|
|
2592
|
-
signer: config.signer,
|
|
2593
|
-
signing: config.signing
|
|
2594
|
-
},
|
|
2595
|
-
{
|
|
2596
|
-
tokenAddress: token.address,
|
|
2597
|
-
recipientAddress: destinationAddress,
|
|
2598
|
-
amount: toTokenUnits(submission.amount.value, token.decimals)
|
|
2599
|
-
}
|
|
2600
|
-
)
|
|
2601
|
-
);
|
|
2602
|
-
if (transferErr) {
|
|
2603
|
-
return await handleSubmissionFailure(
|
|
2604
|
-
{ _data: config._data },
|
|
2605
|
-
created.id,
|
|
2606
|
-
mapCreateError2(fromConvexError(transferErr))
|
|
2607
|
-
);
|
|
2608
|
-
}
|
|
2609
|
-
if (!transferOk.success) {
|
|
2610
|
-
return await handleSubmissionFailure(
|
|
2611
|
-
{ _data: config._data },
|
|
2612
|
-
created.id,
|
|
2613
|
-
new CapxulError({
|
|
2614
|
-
code: "NETWORK_ERROR",
|
|
2615
|
-
message: "Bundler submission did not succeed.",
|
|
2616
|
-
details: {
|
|
2617
|
-
withdrawalId: created.id,
|
|
2618
|
-
txHash: transferOk.txHash,
|
|
2619
|
-
userOpHash: transferOk.userOpHash
|
|
2620
|
-
}
|
|
2621
|
-
})
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
|
-
const [recordErr] = await tryCatch(
|
|
2625
|
-
config._data.mutation(api.withdrawals.mutations.recordSubmitted, {
|
|
2626
|
-
withdrawalId: created.id,
|
|
2627
|
-
txHash: transferOk.txHash,
|
|
2628
|
-
userOpHash: transferOk.userOpHash
|
|
2629
|
-
})
|
|
2630
|
-
);
|
|
2631
|
-
if (recordErr) {
|
|
2632
|
-
return [
|
|
2633
|
-
new CapxulError({
|
|
2634
|
-
code: "NETWORK_ERROR",
|
|
2635
|
-
message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
|
|
2636
|
-
cause: recordErr,
|
|
2637
|
-
details: {
|
|
2638
|
-
withdrawalId: created.id,
|
|
2639
|
-
txHash: transferOk.txHash,
|
|
2640
|
-
userOpHash: transferOk.userOpHash
|
|
2641
|
-
}
|
|
2642
|
-
}),
|
|
2643
|
-
null
|
|
2644
|
-
];
|
|
2645
|
-
}
|
|
2646
|
-
return [null, created];
|
|
2647
|
-
},
|
|
2648
|
-
retrieve: async (withdrawalId) => {
|
|
2649
|
-
if (!config._data) {
|
|
2650
|
-
return stub("withdrawals.retrieve");
|
|
2651
|
-
}
|
|
2652
|
-
const [err, raw] = await tryCatch(
|
|
2653
|
-
config._data.query(api.withdrawals.queries.retrieve, { withdrawalId })
|
|
2654
|
-
);
|
|
2655
|
-
if (err) {
|
|
2656
|
-
return [fromConvexError(err), null];
|
|
2657
|
-
}
|
|
2658
|
-
const withdrawal = raw;
|
|
2659
|
-
if (!withdrawal) {
|
|
2660
|
-
return [
|
|
2661
|
-
new CapxulError({
|
|
2662
|
-
code: "NOT_FOUND",
|
|
2663
|
-
message: `withdrawal ${withdrawalId} not found`
|
|
2664
|
-
}),
|
|
2665
|
-
null
|
|
2666
|
-
];
|
|
2667
|
-
}
|
|
2668
|
-
return [null, withdrawal];
|
|
2669
|
-
},
|
|
2670
|
-
list: async (input) => {
|
|
2671
|
-
if (!config._data) {
|
|
2672
|
-
return stub("withdrawals.list");
|
|
2673
|
-
}
|
|
2674
|
-
const [err, raw] = await tryCatch(
|
|
2675
|
-
config._data.query(api.withdrawals.queries.list, {
|
|
2676
|
-
limit: input?.limit,
|
|
2677
|
-
cursor: input?.cursor
|
|
2678
|
-
})
|
|
2679
|
-
);
|
|
2680
|
-
if (err) {
|
|
2681
|
-
return [fromConvexError(err), null];
|
|
2682
|
-
}
|
|
2683
|
-
return [null, raw];
|
|
2684
|
-
},
|
|
2685
|
-
recordCompleted: async (input) => {
|
|
2686
|
-
if (!config._data) {
|
|
2687
|
-
return stub(
|
|
2688
|
-
"withdrawals.recordCompleted"
|
|
2689
|
-
);
|
|
2690
|
-
}
|
|
2691
|
-
const [err] = await tryCatch(
|
|
2692
|
-
config._data.mutation(api.withdrawals.mutations.recordCompleted, {
|
|
2693
|
-
withdrawalId: input.withdrawalId,
|
|
2694
|
-
txHash: input.txHash
|
|
2695
|
-
})
|
|
2696
|
-
);
|
|
2697
|
-
if (err) {
|
|
2698
|
-
return [
|
|
2699
|
-
mapRecordCompletedError(fromConvexError(err)),
|
|
2700
|
-
null
|
|
2701
|
-
];
|
|
2702
|
-
}
|
|
2703
|
-
return [null, null];
|
|
2704
|
-
}
|
|
2705
|
-
};
|
|
2706
|
-
}
|
|
2707
|
-
function createOrgWithdrawalsClient(config = {}) {
|
|
2708
|
-
return {
|
|
2709
|
-
/**
|
|
2710
|
-
* Org-scope create (Withdrawals v1 W2, #465).
|
|
2711
|
-
*
|
|
2712
|
-
* D6 — returns the `processing` row only. No `transferAsOwner`
|
|
2713
|
-
* tail, no `recordSubmitted` call. Org Safe + Zodiac submission
|
|
2714
|
-
* orchestration ships in W3+.
|
|
2715
|
-
*/
|
|
2716
|
-
create: async (input) => {
|
|
2717
|
-
if (!config._data) {
|
|
2718
|
-
return stub(
|
|
2719
|
-
"organizations.withdrawals.create"
|
|
2720
|
-
);
|
|
2721
|
-
}
|
|
2722
|
-
const [err, raw] = await tryCatch(
|
|
2723
|
-
config._data.mutation(api.withdrawals.mutations.createOrg, {
|
|
2724
|
-
organizationId: input.organizationId,
|
|
2725
|
-
amount: input.amount,
|
|
2726
|
-
destination: {
|
|
2727
|
-
externalAccountId: input.destination.externalAccountId
|
|
2728
|
-
},
|
|
2729
|
-
source: input.source,
|
|
2730
|
-
reference: input.reference,
|
|
2731
|
-
idempotencyKey: input.idempotencyKey
|
|
2732
|
-
})
|
|
2733
|
-
);
|
|
2734
|
-
if (err) {
|
|
2735
|
-
return [mapCreateError2(fromConvexError(err)), null];
|
|
2736
|
-
}
|
|
2737
|
-
const created = raw;
|
|
2738
|
-
if (!created) {
|
|
2739
|
-
return [
|
|
2740
|
-
new CapxulError({
|
|
2741
|
-
code: "NETWORK_ERROR",
|
|
2742
|
-
message: "organizations.withdrawals.create returned no withdrawal resource"
|
|
2743
|
-
}),
|
|
2744
|
-
null
|
|
2745
|
-
];
|
|
2746
|
-
}
|
|
2747
|
-
return [null, created];
|
|
2748
|
-
},
|
|
2749
|
-
retrieve: async (input) => {
|
|
2750
|
-
if (!config._data) {
|
|
2751
|
-
return stub(
|
|
2752
|
-
"organizations.withdrawals.retrieve"
|
|
2753
|
-
);
|
|
2754
|
-
}
|
|
2755
|
-
const [err, raw] = await tryCatch(
|
|
2756
|
-
config._data.query(api.withdrawals.queries.retrieve, {
|
|
2757
|
-
withdrawalId: input.withdrawalId
|
|
2758
|
-
})
|
|
2759
|
-
);
|
|
2760
|
-
if (err) {
|
|
2761
|
-
return [fromConvexError(err), null];
|
|
2762
|
-
}
|
|
2763
|
-
const withdrawal = raw;
|
|
2764
|
-
if (!withdrawal) {
|
|
2765
|
-
return [
|
|
2766
|
-
new CapxulError({
|
|
2767
|
-
code: "NOT_FOUND",
|
|
2768
|
-
message: `withdrawal ${input.withdrawalId} not found`
|
|
2769
|
-
}),
|
|
2770
|
-
null
|
|
2771
|
-
];
|
|
2772
|
-
}
|
|
2773
|
-
const ownerCheck = withdrawal.owner;
|
|
2774
|
-
if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
|
|
2775
|
-
return [
|
|
2776
|
-
new CapxulError({
|
|
2777
|
-
code: "NOT_FOUND",
|
|
2778
|
-
message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
|
|
2779
|
-
}),
|
|
2780
|
-
null
|
|
2781
|
-
];
|
|
2782
|
-
}
|
|
2783
|
-
return [null, withdrawal];
|
|
2784
|
-
},
|
|
2785
|
-
list: async (input) => {
|
|
2786
|
-
if (!config._data) {
|
|
2787
|
-
return stub(
|
|
2788
|
-
"organizations.withdrawals.list"
|
|
2789
|
-
);
|
|
2790
|
-
}
|
|
2791
|
-
const [err, raw] = await tryCatch(
|
|
2792
|
-
config._data.query(api.withdrawals.queries.listOrg, {
|
|
2793
|
-
organizationId: input.organizationId,
|
|
2794
|
-
limit: input.limit,
|
|
2795
|
-
cursor: input.cursor
|
|
2796
|
-
})
|
|
2797
|
-
);
|
|
2798
|
-
if (err) {
|
|
2799
|
-
return [fromConvexError(err), null];
|
|
2800
|
-
}
|
|
2801
|
-
return [null, raw];
|
|
2802
|
-
}
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
|
-
async function handleSubmissionFailure(config, withdrawalId, error) {
|
|
2806
|
-
await bestEffortMarkFailed2(config, withdrawalId, error);
|
|
2807
|
-
return [error, null];
|
|
2808
|
-
}
|
|
2809
|
-
async function bestEffortMarkFailed2(config, withdrawalId, error) {
|
|
2810
|
-
await tryCatch(
|
|
2811
|
-
config._data.mutation(api.withdrawals.mutations.markFailed, {
|
|
2812
|
-
withdrawalId,
|
|
2813
|
-
errorCode: error.code,
|
|
2814
|
-
errorMessage: error.message
|
|
2815
|
-
})
|
|
2816
|
-
);
|
|
2817
|
-
}
|
|
2818
|
-
function mapCreateError2(error) {
|
|
2819
|
-
switch (error.code) {
|
|
2820
|
-
case "NOT_AUTHENTICATED":
|
|
2821
|
-
case "PERMISSION_DENIED":
|
|
2822
|
-
case "INVALID_INPUT":
|
|
2823
|
-
case "INSUFFICIENT_BALANCE":
|
|
2824
|
-
case "IDEMPOTENCY_CONFLICT":
|
|
2825
|
-
case "KYC_REQUIRED":
|
|
2826
|
-
case "POLICY_DENIED":
|
|
2827
|
-
case "RATE_LIMITED":
|
|
2828
|
-
case "NETWORK_ERROR":
|
|
2829
|
-
case "NOT_FOUND":
|
|
2830
|
-
case "VERIFICATION_REQUIRED":
|
|
2831
|
-
return error;
|
|
2832
|
-
default:
|
|
2833
|
-
return new CapxulError({
|
|
2834
|
-
code: "NETWORK_ERROR",
|
|
2835
|
-
message: error.message,
|
|
2836
|
-
cause: error,
|
|
2837
|
-
details: error.details,
|
|
2838
|
-
operationId: error.operationId,
|
|
2839
|
-
correlationId: error.correlationId,
|
|
2840
|
-
retryable: error.retryable
|
|
2841
|
-
});
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
function mapRecordCompletedError(error) {
|
|
2845
|
-
switch (error.code) {
|
|
2846
|
-
case "NOT_AUTHENTICATED":
|
|
2847
|
-
case "PERMISSION_DENIED":
|
|
2848
|
-
case "INVALID_INPUT":
|
|
2849
|
-
case "NOT_FOUND":
|
|
2850
|
-
case "NETWORK_ERROR":
|
|
2851
|
-
case "INTERNAL_ERROR":
|
|
2852
|
-
return error;
|
|
2853
|
-
default:
|
|
2854
|
-
return new CapxulError({
|
|
2855
|
-
code: "NETWORK_ERROR",
|
|
2856
|
-
message: error.message,
|
|
2857
|
-
cause: error,
|
|
2858
|
-
details: error.details,
|
|
2859
|
-
operationId: error.operationId,
|
|
2860
|
-
correlationId: error.correlationId,
|
|
2861
|
-
retryable: error.retryable
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
// src/core/webhook-endpoints.ts
|
|
2867
|
-
function createWebhookEndpointsClient() {
|
|
2868
|
-
return {
|
|
2869
|
-
create: async () => stub(
|
|
2870
|
-
"webhookEndpoints.create"
|
|
2871
|
-
),
|
|
2872
|
-
retrieve: async () => stub("webhookEndpoints.retrieve"),
|
|
2873
|
-
list: async () => stub("webhookEndpoints.list"),
|
|
2874
|
-
remove: async () => stub("webhookEndpoints.remove")
|
|
2875
|
-
};
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
// src/core/webhook-events.ts
|
|
2879
|
-
function createWebhookEventsClient() {
|
|
2880
|
-
return {
|
|
2881
|
-
retrieve: async () => stub("webhookEvents.retrieve"),
|
|
2882
|
-
list: async () => stub("webhookEvents.list")
|
|
2883
|
-
};
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
// src/core/organizations.ts
|
|
2887
|
-
function createOrgExternalAccountsClient(config) {
|
|
2888
|
-
return {
|
|
2889
|
-
create: async (input) => {
|
|
2890
|
-
if (!config._data) {
|
|
2891
|
-
return stub(
|
|
2892
|
-
"organizations.externalAccounts.create"
|
|
2893
|
-
);
|
|
2894
|
-
}
|
|
2895
|
-
const [err, raw] = await tryCatch(
|
|
2896
|
-
config._data.mutation(api.externalAccounts.mutations.createOrg, {
|
|
2897
|
-
organizationId: input.organizationId,
|
|
2898
|
-
kind: input.kind,
|
|
2899
|
-
label: input.label,
|
|
2900
|
-
address: input.address,
|
|
2901
|
-
iban: input.iban,
|
|
2902
|
-
bic: input.bic,
|
|
2903
|
-
accountHolder: input.accountHolder,
|
|
2904
|
-
network: input.network,
|
|
2905
|
-
panToken: input.panToken,
|
|
2906
|
-
last4: input.last4
|
|
2907
|
-
})
|
|
2908
|
-
);
|
|
2909
|
-
if (err) {
|
|
2910
|
-
return [fromConvexError(err), null];
|
|
2911
|
-
}
|
|
2912
|
-
if (!raw) {
|
|
2913
|
-
return [
|
|
2914
|
-
new CapxulError({
|
|
2915
|
-
code: "NOT_FOUND",
|
|
2916
|
-
message: "external_account creation returned no resource"
|
|
2917
|
-
}),
|
|
2918
|
-
null
|
|
2919
|
-
];
|
|
2920
|
-
}
|
|
2921
|
-
return [null, brandExternalAccount(raw)];
|
|
2922
|
-
},
|
|
2923
|
-
list: async (input) => {
|
|
2924
|
-
if (!config._data) {
|
|
2925
|
-
return stub(
|
|
2926
|
-
"organizations.externalAccounts.list"
|
|
2927
|
-
);
|
|
2928
|
-
}
|
|
2929
|
-
const [err, result] = await tryCatch(
|
|
2930
|
-
config._data.query(api.externalAccounts.queries.listOrg, {
|
|
2931
|
-
organizationId: input.organizationId,
|
|
2932
|
-
limit: input.limit,
|
|
2933
|
-
cursor: input.cursor
|
|
2934
|
-
})
|
|
2935
|
-
);
|
|
2936
|
-
if (err) {
|
|
2937
|
-
return [fromConvexError(err), null];
|
|
2938
|
-
}
|
|
2939
|
-
const branded = result.data.map(
|
|
2940
|
-
(row) => brandExternalAccount(row)
|
|
2941
|
-
);
|
|
2942
|
-
return [
|
|
2943
|
-
null,
|
|
2944
|
-
{
|
|
2945
|
-
object: "list",
|
|
2946
|
-
data: branded,
|
|
2947
|
-
page: result.page
|
|
2948
|
-
}
|
|
2949
|
-
];
|
|
2950
|
-
},
|
|
2951
|
-
retrieve: async (input) => {
|
|
2952
|
-
if (!config._data) {
|
|
2953
|
-
return stub(
|
|
2954
|
-
"organizations.externalAccounts.retrieve"
|
|
2955
|
-
);
|
|
2956
|
-
}
|
|
2957
|
-
const [err, raw] = await tryCatch(
|
|
2958
|
-
config._data.query(api.externalAccounts.queries.retrieveOrg, {
|
|
2959
|
-
organizationId: input.organizationId,
|
|
2960
|
-
externalAccountId: input.externalAccountId
|
|
2961
|
-
})
|
|
2962
|
-
);
|
|
2963
|
-
if (err) {
|
|
2964
|
-
return [fromConvexError(err), null];
|
|
2965
|
-
}
|
|
2966
|
-
if (!raw) {
|
|
2967
|
-
return [
|
|
2968
|
-
new CapxulError({
|
|
2969
|
-
code: "NOT_FOUND",
|
|
2970
|
-
message: `external_account ${input.externalAccountId} not found`
|
|
2971
|
-
}),
|
|
2972
|
-
null
|
|
2973
|
-
];
|
|
2974
|
-
}
|
|
2975
|
-
return [null, brandExternalAccount(raw)];
|
|
2976
|
-
},
|
|
2977
|
-
remove: async (input) => {
|
|
2978
|
-
if (!config._data) {
|
|
2979
|
-
return stub("organizations.externalAccounts.remove");
|
|
2980
|
-
}
|
|
2981
|
-
const [err] = await tryCatch(
|
|
2982
|
-
config._data.mutation(api.externalAccounts.mutations.removeOrg, {
|
|
2983
|
-
organizationId: input.organizationId,
|
|
2984
|
-
externalAccountId: input.externalAccountId
|
|
2985
|
-
})
|
|
2986
|
-
);
|
|
2987
|
-
if (err) {
|
|
2988
|
-
return [fromConvexError(err), null];
|
|
2989
|
-
}
|
|
2990
|
-
return [null, void 0];
|
|
2991
|
-
}
|
|
2992
|
-
};
|
|
2993
|
-
}
|
|
2994
|
-
function createOrgSubAccountsClient(config) {
|
|
2995
|
-
return {
|
|
2996
|
-
create: async (input) => {
|
|
2997
|
-
if (!config._data) {
|
|
2998
|
-
return stub(
|
|
2999
|
-
"organizations.subAccounts.create"
|
|
3000
|
-
);
|
|
3001
|
-
}
|
|
3002
|
-
const [err, raw] = await tryCatch(
|
|
3003
|
-
config._data.mutation(api.subAccounts.mutations.create, {
|
|
3004
|
-
parent: {
|
|
3005
|
-
kind: "organization",
|
|
3006
|
-
id: input.organizationId
|
|
3007
|
-
},
|
|
3008
|
-
name: input.name,
|
|
3009
|
-
purpose: input.purpose
|
|
3010
|
-
})
|
|
3011
|
-
);
|
|
3012
|
-
if (err) {
|
|
3013
|
-
return [
|
|
3014
|
-
fromConvexError(err),
|
|
3015
|
-
null
|
|
3016
|
-
];
|
|
3017
|
-
}
|
|
3018
|
-
if (!raw) {
|
|
3019
|
-
return [
|
|
3020
|
-
new CapxulError({
|
|
3021
|
-
code: "NOT_FOUND",
|
|
3022
|
-
message: "sub_account creation returned no resource"
|
|
3023
|
-
}),
|
|
3024
|
-
null
|
|
3025
|
-
];
|
|
3026
|
-
}
|
|
3027
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
3028
|
-
if (brandErr) {
|
|
3029
|
-
return [brandErr, null];
|
|
3030
|
-
}
|
|
3031
|
-
return [null, branded];
|
|
3032
|
-
},
|
|
3033
|
-
list: async (input) => {
|
|
3034
|
-
if (!config._data) {
|
|
3035
|
-
return stub(
|
|
3036
|
-
"organizations.subAccounts.list"
|
|
3037
|
-
);
|
|
3038
|
-
}
|
|
3039
|
-
const [err, rows] = await tryCatch(
|
|
3040
|
-
config._data.query(api.subAccounts.queries.listByOrganization, {
|
|
3041
|
-
organizationId: input.organizationId
|
|
3042
|
-
})
|
|
3043
|
-
);
|
|
3044
|
-
if (err) {
|
|
3045
|
-
return [
|
|
3046
|
-
fromConvexError(err),
|
|
3047
|
-
null
|
|
3048
|
-
];
|
|
3049
|
-
}
|
|
3050
|
-
const branded = [];
|
|
3051
|
-
for (const row of rows) {
|
|
3052
|
-
const [brandErr, value] = tryBrandSubAccount(row);
|
|
3053
|
-
if (brandErr) {
|
|
3054
|
-
return [brandErr, null];
|
|
3055
|
-
}
|
|
3056
|
-
branded.push(value);
|
|
3057
|
-
}
|
|
3058
|
-
return [
|
|
3059
|
-
null,
|
|
3060
|
-
{
|
|
3061
|
-
object: "list",
|
|
3062
|
-
data: branded,
|
|
3063
|
-
page: { hasMore: false }
|
|
3064
|
-
}
|
|
3065
|
-
];
|
|
3066
|
-
},
|
|
3067
|
-
retrieve: async (input) => {
|
|
3068
|
-
if (!config._data) {
|
|
3069
|
-
return stub(
|
|
3070
|
-
"organizations.subAccounts.retrieve"
|
|
3071
|
-
);
|
|
3072
|
-
}
|
|
3073
|
-
const [err, raw] = await tryCatch(
|
|
3074
|
-
config._data.query(api.subAccounts.queries.retrieve, {
|
|
3075
|
-
subAccountId: input.subAccountId
|
|
3076
|
-
})
|
|
3077
|
-
);
|
|
3078
|
-
if (err) {
|
|
3079
|
-
return [
|
|
3080
|
-
fromConvexError(err),
|
|
3081
|
-
null
|
|
3082
|
-
];
|
|
3083
|
-
}
|
|
3084
|
-
if (!raw) {
|
|
3085
|
-
return [
|
|
3086
|
-
new CapxulError({
|
|
3087
|
-
code: "NOT_FOUND",
|
|
3088
|
-
message: `sub_account ${input.subAccountId} not found`
|
|
3089
|
-
}),
|
|
3090
|
-
null
|
|
3091
|
-
];
|
|
3092
|
-
}
|
|
3093
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
3094
|
-
if (brandErr) {
|
|
3095
|
-
return [
|
|
3096
|
-
brandErr,
|
|
3097
|
-
null
|
|
3098
|
-
];
|
|
3099
|
-
}
|
|
3100
|
-
return [null, branded];
|
|
3101
|
-
},
|
|
3102
|
-
remove: async (input) => {
|
|
3103
|
-
if (!config._data) {
|
|
3104
|
-
return stub(
|
|
3105
|
-
"organizations.subAccounts.remove"
|
|
3106
|
-
);
|
|
3107
|
-
}
|
|
3108
|
-
const [err, raw] = await tryCatch(
|
|
3109
|
-
config._data.mutation(api.subAccounts.mutations.archive, {
|
|
3110
|
-
subAccountId: input.subAccountId
|
|
3111
|
-
})
|
|
3112
|
-
);
|
|
3113
|
-
if (err) {
|
|
3114
|
-
return [
|
|
3115
|
-
fromConvexError(err),
|
|
3116
|
-
null
|
|
3117
|
-
];
|
|
3118
|
-
}
|
|
3119
|
-
if (!raw) {
|
|
3120
|
-
return [
|
|
3121
|
-
new CapxulError({
|
|
3122
|
-
code: "NOT_FOUND",
|
|
3123
|
-
message: `sub_account ${input.subAccountId} not found`
|
|
3124
|
-
}),
|
|
3125
|
-
null
|
|
3126
|
-
];
|
|
3127
|
-
}
|
|
3128
|
-
const [brandErr, branded] = tryBrandSubAccount(raw);
|
|
3129
|
-
if (brandErr) {
|
|
3130
|
-
return [
|
|
3131
|
-
brandErr,
|
|
3132
|
-
null
|
|
3133
|
-
];
|
|
3134
|
-
}
|
|
3135
|
-
return [null, branded];
|
|
3136
|
-
}
|
|
3137
|
-
};
|
|
3138
|
-
}
|
|
3139
|
-
function createOrganizationsClient(config = {}) {
|
|
3140
|
-
return {
|
|
3141
|
-
create: async (input) => {
|
|
3142
|
-
if (!config._data) {
|
|
3143
|
-
return stub("organizations.create");
|
|
3144
|
-
}
|
|
3145
|
-
if (input.country !== void 0) {
|
|
3146
|
-
return [
|
|
3147
|
-
new CapxulError({
|
|
3148
|
-
code: "INVALID_INPUT",
|
|
3149
|
-
message: "organizations.create does not support country yet \u2014 backend mutation only accepts name.",
|
|
3150
|
-
details: { field: "country" }
|
|
3151
|
-
}),
|
|
3152
|
-
null
|
|
3153
|
-
];
|
|
3154
|
-
}
|
|
3155
|
-
try {
|
|
3156
|
-
const orgId = await config._data.mutation(api.org.mutations.create, {
|
|
3157
|
-
name: input.name
|
|
3158
|
-
});
|
|
3159
|
-
const org = await config._data.query(api.org.queries.retrieve, {
|
|
3160
|
-
orgId
|
|
3161
|
-
});
|
|
3162
|
-
if (!org) {
|
|
3163
|
-
return [
|
|
3164
|
-
new CapxulError({
|
|
3165
|
-
code: "NETWORK_ERROR",
|
|
3166
|
-
message: "organization created but could not be retrieved"
|
|
3167
|
-
}),
|
|
3168
|
-
null
|
|
3169
|
-
];
|
|
3170
|
-
}
|
|
3171
|
-
return [null, org];
|
|
3172
|
-
} catch (cause) {
|
|
3173
|
-
return [fromConvexError(cause), null];
|
|
3174
|
-
}
|
|
3175
|
-
},
|
|
3176
|
-
retrieve: async (organizationId) => {
|
|
3177
|
-
if (!config._data) {
|
|
3178
|
-
return stub("organizations.retrieve");
|
|
3179
|
-
}
|
|
3180
|
-
try {
|
|
3181
|
-
const orgId = organizationId.replace(/^org_/, "");
|
|
3182
|
-
const org = await config._data.query(api.org.queries.retrieve, {
|
|
3183
|
-
orgId
|
|
3184
|
-
});
|
|
3185
|
-
if (!org) {
|
|
3186
|
-
return [
|
|
3187
|
-
new CapxulError({
|
|
3188
|
-
code: "NOT_FOUND",
|
|
3189
|
-
message: `organization ${organizationId} not found`
|
|
3190
|
-
}),
|
|
3191
|
-
null
|
|
3192
|
-
];
|
|
3193
|
-
}
|
|
3194
|
-
return [null, org];
|
|
3195
|
-
} catch (cause) {
|
|
3196
|
-
return [fromConvexError(cause), null];
|
|
3197
|
-
}
|
|
3198
|
-
},
|
|
3199
|
-
list: async (input) => {
|
|
3200
|
-
if (!config._data) {
|
|
3201
|
-
return stub("organizations.list");
|
|
3202
|
-
}
|
|
3203
|
-
try {
|
|
3204
|
-
const page = await config._data.query(api.org.queries.list, {
|
|
3205
|
-
limit: input?.limit,
|
|
3206
|
-
cursor: input?.cursor
|
|
3207
|
-
});
|
|
3208
|
-
const result = {
|
|
3209
|
-
object: "list",
|
|
3210
|
-
data: page.data,
|
|
3211
|
-
page: {
|
|
3212
|
-
hasMore: page.hasMore,
|
|
3213
|
-
cursor: page.nextCursor
|
|
3214
|
-
}
|
|
3215
|
-
};
|
|
3216
|
-
return [null, result];
|
|
3217
|
-
} catch (cause) {
|
|
3218
|
-
return [fromConvexError(cause), null];
|
|
3219
|
-
}
|
|
3220
|
-
},
|
|
3221
|
-
update: async (input) => {
|
|
3222
|
-
if (!config._data) {
|
|
3223
|
-
return stub("organizations.update");
|
|
3224
|
-
}
|
|
3225
|
-
try {
|
|
3226
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3227
|
-
const org = await config._data.mutation(api.org.mutations.update, {
|
|
3228
|
-
orgId,
|
|
3229
|
-
name: input.name
|
|
3230
|
-
});
|
|
3231
|
-
if (!org) {
|
|
3232
|
-
return [
|
|
3233
|
-
new CapxulError({
|
|
3234
|
-
code: "NOT_FOUND",
|
|
3235
|
-
message: `organization ${input.organizationId} not found`
|
|
3236
|
-
}),
|
|
3237
|
-
null
|
|
3238
|
-
];
|
|
3239
|
-
}
|
|
3240
|
-
return [null, org];
|
|
3241
|
-
} catch (cause) {
|
|
3242
|
-
return [fromConvexError(cause), null];
|
|
3243
|
-
}
|
|
3244
|
-
},
|
|
3245
|
-
safes: {
|
|
3246
|
-
retrieve: async (input) => {
|
|
3247
|
-
if (!config._data) {
|
|
3248
|
-
return stub("organizations.safes.retrieve");
|
|
3249
|
-
}
|
|
3250
|
-
try {
|
|
3251
|
-
const safe = await config._data.query(
|
|
3252
|
-
api.safe.queries.retrieveOrganizationSafe,
|
|
3253
|
-
input
|
|
3254
|
-
);
|
|
3255
|
-
if (!safe) {
|
|
3256
|
-
return [
|
|
3257
|
-
new CapxulError({
|
|
3258
|
-
code: "NOT_FOUND",
|
|
3259
|
-
message: `safe ${input.safeId} not found`
|
|
3260
|
-
}),
|
|
3261
|
-
null
|
|
3262
|
-
];
|
|
3263
|
-
}
|
|
3264
|
-
return [null, safe];
|
|
3265
|
-
} catch (cause) {
|
|
3266
|
-
return [
|
|
3267
|
-
fromConvexError(cause),
|
|
3268
|
-
null
|
|
3269
|
-
];
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
},
|
|
3273
|
-
treasury: {
|
|
3274
|
-
retrieve: async (organizationId) => {
|
|
3275
|
-
if (!config._data) {
|
|
3276
|
-
return stub(
|
|
3277
|
-
"organizations.treasury.retrieve"
|
|
3278
|
-
);
|
|
3279
|
-
}
|
|
3280
|
-
try {
|
|
3281
|
-
const orgId = organizationId.replace(/^org_/, "");
|
|
3282
|
-
const raw = await config._data.query(
|
|
3283
|
-
api.safe.queries.getOrgTreasuryBalance,
|
|
3284
|
-
{ orgId }
|
|
3285
|
-
);
|
|
3286
|
-
if (!raw) {
|
|
3287
|
-
return [
|
|
3288
|
-
new CapxulError({
|
|
3289
|
-
code: "NOT_FOUND",
|
|
3290
|
-
message: `treasury for organization ${organizationId} not found`
|
|
3291
|
-
}),
|
|
3292
|
-
null
|
|
3293
|
-
];
|
|
3294
|
-
}
|
|
3295
|
-
const treasury = {
|
|
3296
|
-
object: "treasury",
|
|
3297
|
-
id: toTreasuryId(`try_${orgId}`),
|
|
3298
|
-
organizationId,
|
|
3299
|
-
status: "active",
|
|
3300
|
-
safeId: toSafeId(
|
|
3301
|
-
`safe_${raw.safeAddress.replace(/^0x/, "").toLowerCase()}`
|
|
3302
|
-
),
|
|
3303
|
-
totalBalance: { value: "0", currency: "USD" },
|
|
3304
|
-
positions: raw.tokens.map((t) => ({
|
|
3305
|
-
symbol: t.symbol,
|
|
3306
|
-
contractAddress: t.tokenAddress,
|
|
3307
|
-
amount: t.balance
|
|
3308
|
-
})),
|
|
3309
|
-
asOf: raw.lastUpdatedAt ? new Date(raw.lastUpdatedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
3310
|
-
};
|
|
3311
|
-
return [null, treasury];
|
|
3312
|
-
} catch (cause) {
|
|
3313
|
-
return [
|
|
3314
|
-
fromConvexError(cause),
|
|
3315
|
-
null
|
|
3316
|
-
];
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
},
|
|
3320
|
-
members: {
|
|
3321
|
-
list: async (input) => {
|
|
3322
|
-
if (!config._data?.action) {
|
|
3323
|
-
return stub("organizations.members.list");
|
|
3324
|
-
}
|
|
3325
|
-
try {
|
|
3326
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3327
|
-
const page = await config._data.action(api.org.actions.membersList, {
|
|
3328
|
-
organizationId: orgId,
|
|
3329
|
-
status: input.status,
|
|
3330
|
-
limit: input.limit,
|
|
3331
|
-
cursor: input.cursor
|
|
3332
|
-
});
|
|
3333
|
-
return [null, page];
|
|
3334
|
-
} catch (cause) {
|
|
3335
|
-
return [fromConvexError(cause), null];
|
|
3336
|
-
}
|
|
3337
|
-
},
|
|
3338
|
-
retrieve: async (input) => {
|
|
3339
|
-
if (!config._data?.action) {
|
|
3340
|
-
return stub("organizations.members.retrieve");
|
|
3341
|
-
}
|
|
3342
|
-
try {
|
|
3343
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3344
|
-
const memberId = input.memberId.replace(/^mb_/, "");
|
|
3345
|
-
const member = await config._data.action(
|
|
3346
|
-
api.org.actions.retrieveMember,
|
|
3347
|
-
{
|
|
3348
|
-
organizationId: orgId,
|
|
3349
|
-
memberId
|
|
3350
|
-
}
|
|
3351
|
-
);
|
|
3352
|
-
return [null, member];
|
|
3353
|
-
} catch (cause) {
|
|
3354
|
-
return [fromConvexError(cause), null];
|
|
3355
|
-
}
|
|
3356
|
-
},
|
|
3357
|
-
invite: async (input) => {
|
|
3358
|
-
if (!config._data?.action) {
|
|
3359
|
-
return stub(
|
|
3360
|
-
"organizations.members.invite"
|
|
3361
|
-
);
|
|
3362
|
-
}
|
|
3363
|
-
try {
|
|
3364
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3365
|
-
const result = await config._data.action(
|
|
3366
|
-
api.org.actions.inviteMember,
|
|
3367
|
-
{
|
|
3368
|
-
organizationId: orgId,
|
|
3369
|
-
email: input.email,
|
|
3370
|
-
role: input.role
|
|
3371
|
-
}
|
|
3372
|
-
);
|
|
3373
|
-
return [null, result];
|
|
3374
|
-
} catch (cause) {
|
|
3375
|
-
return [fromConvexError(cause), null];
|
|
3376
|
-
}
|
|
3377
|
-
},
|
|
3378
|
-
accept: async (input) => {
|
|
3379
|
-
if (!config._data?.action) {
|
|
3380
|
-
return stub("organizations.members.accept");
|
|
3381
|
-
}
|
|
3382
|
-
try {
|
|
3383
|
-
const member = await config._data.action(
|
|
3384
|
-
api.org.actions.acceptInvitation,
|
|
3385
|
-
{ token: input.token }
|
|
3386
|
-
);
|
|
3387
|
-
return [null, member];
|
|
3388
|
-
} catch (cause) {
|
|
3389
|
-
return [fromConvexError(cause), null];
|
|
3390
|
-
}
|
|
3391
|
-
},
|
|
3392
|
-
updateRole: async (input) => {
|
|
3393
|
-
if (!config._data?.action) {
|
|
3394
|
-
return stub("organizations.members.updateRole");
|
|
3395
|
-
}
|
|
3396
|
-
try {
|
|
3397
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3398
|
-
const memberId = input.memberId.replace(/^mb_/, "");
|
|
3399
|
-
const member = await config._data.action(
|
|
3400
|
-
api.org.actions.updateMemberRole,
|
|
3401
|
-
{
|
|
3402
|
-
organizationId: orgId,
|
|
3403
|
-
memberId,
|
|
3404
|
-
role: input.role
|
|
3405
|
-
}
|
|
3406
|
-
);
|
|
3407
|
-
return [null, member];
|
|
3408
|
-
} catch (cause) {
|
|
3409
|
-
return [fromConvexError(cause), null];
|
|
3410
|
-
}
|
|
3411
|
-
},
|
|
3412
|
-
revoke: async (input) => {
|
|
3413
|
-
if (!config._data?.action) {
|
|
3414
|
-
return stub("organizations.members.revoke");
|
|
3415
|
-
}
|
|
3416
|
-
try {
|
|
3417
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3418
|
-
const memberId = input.memberId.replace(/^mb_/, "");
|
|
3419
|
-
const member = await config._data.action(
|
|
3420
|
-
api.org.actions.revokeMember,
|
|
3421
|
-
{
|
|
3422
|
-
organizationId: orgId,
|
|
3423
|
-
memberId
|
|
3424
|
-
}
|
|
3425
|
-
);
|
|
3426
|
-
return [null, member];
|
|
3427
|
-
} catch (cause) {
|
|
3428
|
-
return [fromConvexError(cause), null];
|
|
3429
|
-
}
|
|
3430
|
-
},
|
|
3431
|
-
remove: async (input) => {
|
|
3432
|
-
if (!config._data?.action) {
|
|
3433
|
-
return stub("organizations.members.remove");
|
|
3434
|
-
}
|
|
3435
|
-
try {
|
|
3436
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3437
|
-
const memberId = input.memberId.replace(/^mb_/, "");
|
|
3438
|
-
await config._data.action(api.org.actions.removeMember, {
|
|
3439
|
-
organizationId: orgId,
|
|
3440
|
-
memberId
|
|
3441
|
-
});
|
|
3442
|
-
return [null, void 0];
|
|
3443
|
-
} catch (cause) {
|
|
3444
|
-
return [fromConvexError(cause), null];
|
|
3445
|
-
}
|
|
3446
|
-
},
|
|
3447
|
-
resend: async (input) => {
|
|
3448
|
-
if (!config._data?.action) {
|
|
3449
|
-
return stub(
|
|
3450
|
-
"organizations.members.resend"
|
|
3451
|
-
);
|
|
3452
|
-
}
|
|
3453
|
-
try {
|
|
3454
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3455
|
-
const memberId = input.memberId.replace(/^mb_/, "");
|
|
3456
|
-
const result = await config._data.action(
|
|
3457
|
-
api.org.actions.resendInvitation,
|
|
3458
|
-
{
|
|
3459
|
-
organizationId: orgId,
|
|
3460
|
-
memberId
|
|
3461
|
-
}
|
|
3462
|
-
);
|
|
3463
|
-
return [null, result];
|
|
3464
|
-
} catch (cause) {
|
|
3465
|
-
return [fromConvexError(cause), null];
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
},
|
|
3469
|
-
apiKeys: createApiKeysClient(),
|
|
3470
|
-
subAccounts: createOrgSubAccountsClient(config),
|
|
3471
|
-
externalAccounts: createOrgExternalAccountsClient(config),
|
|
3472
|
-
balanceLedger: {
|
|
3473
|
-
list: async (input) => {
|
|
3474
|
-
if (!config._data) {
|
|
3475
|
-
return stub(
|
|
3476
|
-
"organizations.balanceLedger.list"
|
|
3477
|
-
);
|
|
3478
|
-
}
|
|
3479
|
-
try {
|
|
3480
|
-
const orgId = input.organizationId.replace(/^org_/, "");
|
|
3481
|
-
const page = await config._data.query(
|
|
3482
|
-
api.balanceLedger.queries.listForOrg,
|
|
3483
|
-
{ orgId, limit: input.limit, cursor: input.cursor }
|
|
3484
|
-
);
|
|
3485
|
-
return [null, page];
|
|
3486
|
-
} catch (cause) {
|
|
3487
|
-
return [fromConvexError(cause), null];
|
|
3488
|
-
}
|
|
3489
|
-
},
|
|
3490
|
-
retrieve: async (input) => {
|
|
3491
|
-
if (!config._data) {
|
|
3492
|
-
return stub(
|
|
3493
|
-
"organizations.balanceLedger.retrieve"
|
|
3494
|
-
);
|
|
3495
|
-
}
|
|
3496
|
-
try {
|
|
3497
|
-
const entry = await config._data.query(
|
|
3498
|
-
api.balanceLedger.queries.retrieve,
|
|
3499
|
-
{ entryId: input.entryId }
|
|
3500
|
-
);
|
|
3501
|
-
if (!entry) {
|
|
3502
|
-
return [
|
|
3503
|
-
new CapxulError({
|
|
3504
|
-
code: "NOT_FOUND",
|
|
3505
|
-
message: `balance_ledger_entry ${input.entryId} not found`
|
|
3506
|
-
}),
|
|
3507
|
-
null
|
|
3508
|
-
];
|
|
3509
|
-
}
|
|
3510
|
-
return [null, entry];
|
|
3511
|
-
} catch (cause) {
|
|
3512
|
-
return [fromConvexError(cause), null];
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
},
|
|
3516
|
-
payments: createOrgPaymentsClient(),
|
|
3517
|
-
transfers: createOrgTransfersClient(),
|
|
3518
|
-
withdrawals: createOrgWithdrawalsClient(config),
|
|
3519
|
-
documents: createOrgDocumentsClient(config),
|
|
3520
|
-
webhookEndpoints: createWebhookEndpointsClient(),
|
|
3521
|
-
webhookEvents: createWebhookEventsClient()
|
|
3522
|
-
};
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
// src/core/token-transfers.ts
|
|
3526
|
-
var toTokenTransferId = (raw) => {
|
|
3527
|
-
if (typeof raw !== "string" || raw.length === 0) {
|
|
3528
|
-
throw new Error(
|
|
3529
|
-
`Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
|
|
3530
|
-
);
|
|
3531
|
-
}
|
|
3532
|
-
return raw;
|
|
3533
|
-
};
|
|
3534
|
-
function brandRow(row) {
|
|
3535
|
-
return {
|
|
3536
|
-
...row,
|
|
3537
|
-
id: toTokenTransferId(row.id)
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
|
-
function createTokenTransfersClient(config = {}) {
|
|
3541
|
-
return {
|
|
3542
|
-
list: async (input) => {
|
|
3543
|
-
if (!config._data) {
|
|
3544
|
-
return stub("tokenTransfers.list");
|
|
3545
|
-
}
|
|
3546
|
-
try {
|
|
3547
|
-
const raw = await config._data.query(
|
|
3548
|
-
api.tokenTransfers.queries.list,
|
|
3549
|
-
{
|
|
3550
|
-
limit: input?.limit,
|
|
3551
|
-
cursor: input?.cursor,
|
|
3552
|
-
direction: input?.direction
|
|
3553
|
-
}
|
|
3554
|
-
);
|
|
3555
|
-
if (!raw) {
|
|
3556
|
-
return [
|
|
3557
|
-
new CapxulError({
|
|
3558
|
-
code: "NOT_AUTHENTICATED",
|
|
3559
|
-
message: "tokenTransfers.list requires an authenticated session."
|
|
3560
|
-
}),
|
|
3561
|
-
null
|
|
3562
|
-
];
|
|
3563
|
-
}
|
|
3564
|
-
return [
|
|
3565
|
-
null,
|
|
3566
|
-
{
|
|
3567
|
-
object: "list",
|
|
3568
|
-
data: raw.items.map(brandRow),
|
|
3569
|
-
page: {
|
|
3570
|
-
hasMore: raw.hasMore,
|
|
3571
|
-
nextCursor: raw.nextCursor
|
|
3572
|
-
},
|
|
3573
|
-
displayCurrency: raw.displayCurrency
|
|
3574
|
-
}
|
|
3575
|
-
];
|
|
3576
|
-
} catch (cause) {
|
|
3577
|
-
return [fromConvexError(cause), null];
|
|
3578
|
-
}
|
|
3579
|
-
},
|
|
3580
|
-
retrieve: async (input) => {
|
|
3581
|
-
if (!config._data) {
|
|
3582
|
-
return stub("tokenTransfers.retrieve");
|
|
3583
|
-
}
|
|
3584
|
-
try {
|
|
3585
|
-
const raw = await config._data.query(
|
|
3586
|
-
api.tokenTransfers.queries.getByTxLogIndex,
|
|
3587
|
-
{
|
|
3588
|
-
txHash: input.txHash,
|
|
3589
|
-
logIndex: input.logIndex,
|
|
3590
|
-
chainId: input.chainId
|
|
3591
|
-
}
|
|
3592
|
-
);
|
|
3593
|
-
if (!raw) {
|
|
3594
|
-
return [
|
|
3595
|
-
new CapxulError({
|
|
3596
|
-
code: "NOT_FOUND",
|
|
3597
|
-
message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
|
|
3598
|
-
details: {
|
|
3599
|
-
txHash: input.txHash,
|
|
3600
|
-
logIndex: input.logIndex,
|
|
3601
|
-
chainId: input.chainId
|
|
3602
|
-
}
|
|
3603
|
-
}),
|
|
3604
|
-
null
|
|
3605
|
-
];
|
|
3606
|
-
}
|
|
3607
|
-
return [null, brandRow(raw)];
|
|
3608
|
-
} catch (cause) {
|
|
3609
|
-
return [fromConvexError(cause), null];
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
};
|
|
3613
|
-
}
|
|
3614
|
-
|
|
3615
|
-
// src/core/virtual-accounts.ts
|
|
3616
|
-
function createVirtualAccountsClient() {
|
|
3617
|
-
return {
|
|
3618
|
-
create: async () => stub("virtualAccounts.create"),
|
|
3619
|
-
retrieve: async () => stub("virtualAccounts.retrieve"),
|
|
3620
|
-
list: async () => stub("virtualAccounts.list"),
|
|
3621
|
-
remove: async () => stub("virtualAccounts.remove")
|
|
3622
|
-
};
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
|
-
// src/core/virtual-cards.ts
|
|
3626
|
-
function createVirtualCardsClient() {
|
|
3627
|
-
return {
|
|
3628
|
-
create: async () => stub("virtualCards.create"),
|
|
3629
|
-
retrieve: async () => stub("virtualCards.retrieve"),
|
|
3630
|
-
list: async () => stub("virtualCards.list"),
|
|
3631
|
-
freeze: async () => stub("virtualCards.freeze"),
|
|
3632
|
-
unfreeze: async () => stub("virtualCards.unfreeze"),
|
|
3633
|
-
cancel: async () => stub("virtualCards.cancel")
|
|
3634
|
-
};
|
|
3635
|
-
}
|
|
3636
|
-
function createProvisioningMachine(client) {
|
|
3637
|
-
return xstate.setup({
|
|
3638
|
-
types: {},
|
|
3639
|
-
actors: {
|
|
3640
|
-
provisionPersonal: xstate.fromPromise(async ({ input }) => {
|
|
3641
|
-
const [error, account] = await client.accounts.provisionPersonal(
|
|
3642
|
-
input.input
|
|
3643
|
-
);
|
|
3644
|
-
if (error) throw error;
|
|
3645
|
-
return account;
|
|
3646
|
-
})
|
|
3647
|
-
},
|
|
3648
|
-
guards: {
|
|
3649
|
-
hasInput: ({ context }) => context.input !== null
|
|
3650
|
-
},
|
|
3651
|
-
actions: {
|
|
3652
|
-
trackWalletCreated: ({ context }) => {
|
|
3653
|
-
const provider = context.input?.signerProvider;
|
|
3654
|
-
if (!provider) return;
|
|
3655
|
-
track("provisioning_wallet_created", {
|
|
3656
|
-
eoa_address: provider.signerAddress
|
|
3657
|
-
});
|
|
3658
|
-
},
|
|
3659
|
-
trackSafeCreated: ({ context }) => {
|
|
3660
|
-
const provider = context.input?.signerProvider;
|
|
3661
|
-
if (!provider) return;
|
|
3662
|
-
track("provisioning_safe_created", {
|
|
3663
|
-
safe_address: deriveSafeAddress2(provider.signerAddress)
|
|
3664
|
-
});
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
}).createMachine({
|
|
3668
|
-
id: "provisioning",
|
|
3669
|
-
initial: "starting",
|
|
3670
|
-
context: ({ input }) => ({
|
|
3671
|
-
input: input?.input ?? null,
|
|
3672
|
-
account: null,
|
|
3673
|
-
error: null
|
|
3674
|
-
}),
|
|
3675
|
-
states: {
|
|
3676
|
-
// Transient routing state: skip `idle` when input was provided
|
|
3677
|
-
// at creation time (the invoked-by-parent path).
|
|
3678
|
-
starting: {
|
|
3679
|
-
always: [
|
|
3680
|
-
{ guard: "hasInput", target: "running" },
|
|
3681
|
-
{ target: "idle" }
|
|
3682
|
-
]
|
|
3683
|
-
},
|
|
3684
|
-
idle: {
|
|
3685
|
-
on: {
|
|
3686
|
-
START: {
|
|
3687
|
-
target: "running",
|
|
3688
|
-
actions: xstate.assign({
|
|
3689
|
-
input: ({ event }) => event.input,
|
|
3690
|
-
account: () => null,
|
|
3691
|
-
error: () => null
|
|
3692
|
-
})
|
|
3693
|
-
}
|
|
3694
|
-
}
|
|
3695
|
-
},
|
|
3696
|
-
running: {
|
|
3697
|
-
invoke: {
|
|
3698
|
-
src: "provisionPersonal",
|
|
3699
|
-
input: ({ context }) => ({
|
|
3700
|
-
input: requireProvisionInput(context)
|
|
3701
|
-
}),
|
|
3702
|
-
onDone: {
|
|
3703
|
-
target: "done",
|
|
3704
|
-
actions: xstate.assign({ account: ({ event }) => event.output })
|
|
3705
|
-
},
|
|
3706
|
-
onError: {
|
|
3707
|
-
target: "error",
|
|
3708
|
-
actions: xstate.assign({ error: ({ event }) => errorFromEvent(event) })
|
|
3709
|
-
}
|
|
3710
|
-
},
|
|
3711
|
-
after: {
|
|
3712
|
-
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
3713
|
-
target: "error",
|
|
3714
|
-
actions: xstate.assign({ error: () => timeoutError() })
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
},
|
|
3718
|
-
done: {
|
|
3719
|
-
type: "final",
|
|
3720
|
-
entry: ["trackWalletCreated", "trackSafeCreated"]
|
|
3721
|
-
},
|
|
3722
|
-
error: {
|
|
3723
|
-
type: "final"
|
|
3724
|
-
}
|
|
3725
|
-
},
|
|
3726
|
-
/**
|
|
3727
|
-
* Root-level output mapper — fires when the machine reaches any
|
|
3728
|
-
* top-level `final` state (`done` or `error`). The parent receives
|
|
3729
|
-
* this payload on its `onDone` transition and branches via guards
|
|
3730
|
-
* on `event.output.error`.
|
|
3731
|
-
*/
|
|
3732
|
-
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError() }
|
|
3733
|
-
});
|
|
3734
|
-
}
|
|
3735
|
-
function requireProvisionInput(context) {
|
|
3736
|
-
if (!context.input) {
|
|
3737
|
-
throw Errors.invalidInput(
|
|
3738
|
-
"input",
|
|
3739
|
-
"Provisioning flow advanced to running without input captured in context."
|
|
3740
|
-
);
|
|
3741
|
-
}
|
|
3742
|
-
return context.input;
|
|
3743
|
-
}
|
|
3744
|
-
function errorFromEvent(event) {
|
|
3745
|
-
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
3746
|
-
if (cause instanceof CapxulError) return cause;
|
|
3747
|
-
if (cause instanceof CapxulError2) return cause;
|
|
3748
|
-
return Errors.providerError("provisioning", "flow", cause);
|
|
3749
|
-
}
|
|
3750
|
-
function timeoutError() {
|
|
3751
|
-
return Errors.providerError(
|
|
3752
|
-
"provisioning",
|
|
3753
|
-
"flow",
|
|
3754
|
-
new Error(`timeout: running exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
|
|
3755
|
-
);
|
|
3756
|
-
}
|
|
3757
|
-
|
|
3758
|
-
// src/flows/onboarding.ts
|
|
3759
|
-
function createOnboardingFlowMachine(client) {
|
|
3760
|
-
const provisioningMachine = createProvisioningMachine(client);
|
|
3761
|
-
return xstate.setup({
|
|
3762
|
-
types: {},
|
|
3763
|
-
actors: {
|
|
3764
|
-
provisioningMachine
|
|
3765
|
-
},
|
|
3766
|
-
guards: {
|
|
3767
|
-
isOrg: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind !== "account",
|
|
3768
|
-
missingSigner: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind === "account" && !event.input.signerProvider,
|
|
3769
|
-
childReportedError: ({ event }) => {
|
|
3770
|
-
if (typeof event !== "object" || event === null || !("output" in event)) {
|
|
3771
|
-
return false;
|
|
3772
|
-
}
|
|
3773
|
-
const output = event.output;
|
|
3774
|
-
return typeof output === "object" && output !== null && "error" in output && output.error != null;
|
|
3775
|
-
}
|
|
3776
|
-
},
|
|
3777
|
-
actions: {
|
|
3778
|
-
// Always-on for any START_PROVISIONING transition.
|
|
3779
|
-
assignOperationIdAndInput: xstate.assign({
|
|
3780
|
-
operationId: () => generateOperationId(),
|
|
3781
|
-
input: ({ event }) => event.type === "START_PROVISIONING" ? event.input : null,
|
|
3782
|
-
error: () => null,
|
|
3783
|
-
account: () => null
|
|
3784
|
-
}),
|
|
3785
|
-
// Pre-actor rejection branches stamp the synchronous error.
|
|
3786
|
-
assignOrgNotImplemented: xstate.assign({
|
|
3787
|
-
error: () => new CapxulError({
|
|
3788
|
-
code: "NOT_IMPLEMENTED",
|
|
3789
|
-
message: "useOnboardingFlow organization onboarding is not implemented in this local-private-key slice."
|
|
3790
|
-
})
|
|
3791
|
-
}),
|
|
3792
|
-
assignMissingSigner: xstate.assign({
|
|
3793
|
-
error: () => new CapxulError({
|
|
3794
|
-
code: "INVALID_INPUT",
|
|
3795
|
-
message: "useOnboardingFlow requires a signerProvider for account onboarding."
|
|
3796
|
-
})
|
|
3797
|
-
}),
|
|
3798
|
-
// Telemetry actions — order matters; the source fires the
|
|
3799
|
-
// "submitted" track BEFORE the rejection track on validation
|
|
3800
|
-
// errors, and the happy path follows the documented sequence.
|
|
3801
|
-
trackOrgSubmitted: ({ event }) => {
|
|
3802
|
-
if (event.type !== "START_PROVISIONING") return;
|
|
3803
|
-
track("onboarding_org_submitted", {
|
|
3804
|
-
country: event.input.country ?? "unknown"
|
|
3805
|
-
});
|
|
3806
|
-
},
|
|
3807
|
-
trackPersonalSubmitted: ({ event }) => {
|
|
3808
|
-
if (event.type !== "START_PROVISIONING") return;
|
|
3809
|
-
track("onboarding_personal_submitted", {
|
|
3810
|
-
country: event.input.country ?? "unknown",
|
|
3811
|
-
wallet_count: event.input.signerProvider ? 1 : 0
|
|
3812
|
-
});
|
|
3813
|
-
},
|
|
3814
|
-
trackOrgNotImplementedError: () => {
|
|
3815
|
-
track("onboarding_wallet_error", {
|
|
3816
|
-
step: "profile",
|
|
3817
|
-
reason: "organization_not_implemented"
|
|
3818
|
-
});
|
|
3819
|
-
},
|
|
3820
|
-
trackMissingSignerError: () => {
|
|
3821
|
-
track("onboarding_wallet_error", {
|
|
3822
|
-
step: "profile",
|
|
3823
|
-
reason: "missing_signer_provider"
|
|
3824
|
-
});
|
|
3825
|
-
},
|
|
3826
|
-
trackWalletCreating: () => {
|
|
3827
|
-
track("onboarding_wallet_creating");
|
|
3828
|
-
},
|
|
3829
|
-
trackOnboardingCompleted: () => {
|
|
3830
|
-
track("onboarding_completed", { account_type: "personal" });
|
|
3831
|
-
},
|
|
3832
|
-
trackProvisioningFailed: ({ context }) => {
|
|
3833
|
-
const code = context.error?.code ?? "UNKNOWN";
|
|
3834
|
-
track("onboarding_wallet_error", {
|
|
3835
|
-
step: "provision_personal",
|
|
3836
|
-
reason: code
|
|
3837
|
-
});
|
|
3838
|
-
},
|
|
3839
|
-
assignChildReportedError: xstate.assign({
|
|
3840
|
-
error: ({ event }) => extractChildErrorOrFallback(event)
|
|
3841
|
-
}),
|
|
3842
|
-
assignChildThrown: xstate.assign({
|
|
3843
|
-
error: ({ event }) => errorFromEvent2(event)
|
|
3844
|
-
}),
|
|
3845
|
-
assignAccountFromChild: xstate.assign({
|
|
3846
|
-
account: ({ event }) => extractChildAccountOrNull(event)
|
|
3847
|
-
}),
|
|
3848
|
-
resetContext: xstate.assign({
|
|
3849
|
-
input: () => null,
|
|
3850
|
-
operationId: () => null,
|
|
3851
|
-
account: () => null,
|
|
3852
|
-
error: () => null
|
|
3853
|
-
})
|
|
3854
|
-
}
|
|
3855
|
-
}).createMachine({
|
|
3856
|
-
id: "onboarding",
|
|
3857
|
-
initial: "profile",
|
|
3858
|
-
context: {
|
|
3859
|
-
input: null,
|
|
3860
|
-
operationId: null,
|
|
3861
|
-
account: null,
|
|
3862
|
-
error: null
|
|
3863
|
-
},
|
|
3864
|
-
states: {
|
|
3865
|
-
profile: {
|
|
3866
|
-
on: {
|
|
3867
|
-
START_PROVISIONING: [
|
|
3868
|
-
{
|
|
3869
|
-
guard: "isOrg",
|
|
3870
|
-
target: "error",
|
|
3871
|
-
actions: [
|
|
3872
|
-
"assignOperationIdAndInput",
|
|
3873
|
-
"trackOrgSubmitted",
|
|
3874
|
-
"assignOrgNotImplemented",
|
|
3875
|
-
"trackOrgNotImplementedError"
|
|
3876
|
-
]
|
|
3877
|
-
},
|
|
3878
|
-
{
|
|
3879
|
-
guard: "missingSigner",
|
|
3880
|
-
target: "error",
|
|
3881
|
-
actions: [
|
|
3882
|
-
"assignOperationIdAndInput",
|
|
3883
|
-
"trackPersonalSubmitted",
|
|
3884
|
-
"assignMissingSigner",
|
|
3885
|
-
"trackMissingSignerError"
|
|
3886
|
-
]
|
|
3887
|
-
},
|
|
3888
|
-
{
|
|
3889
|
-
target: "provisioning",
|
|
3890
|
-
actions: [
|
|
3891
|
-
"assignOperationIdAndInput",
|
|
3892
|
-
"trackPersonalSubmitted"
|
|
3893
|
-
]
|
|
3894
|
-
}
|
|
3895
|
-
]
|
|
3896
|
-
}
|
|
3897
|
-
},
|
|
3898
|
-
provisioning: {
|
|
3899
|
-
entry: ["trackWalletCreating"],
|
|
3900
|
-
invoke: {
|
|
3901
|
-
src: "provisioningMachine",
|
|
3902
|
-
input: ({ context }) => ({
|
|
3903
|
-
input: requireProvisionInput2(context)
|
|
3904
|
-
}),
|
|
3905
|
-
// The child machine reaches a top-level `final` state for
|
|
3906
|
-
// both success and failure, so the parent's `onDone` fires
|
|
3907
|
-
// in both cases. We branch via a guard on
|
|
3908
|
-
// `event.output.error`. The child machine has its own
|
|
3909
|
-
// `FLOW_INVOKE_TIMEOUT_MS` timer that ends in a final
|
|
3910
|
-
// `error` state on timeout — that signal flows back through
|
|
3911
|
-
// `onDone` + the `childReportedError` guard. The previous
|
|
3912
|
-
// duplicate parent `after: FLOW_INVOKE_TIMEOUT_MS` was
|
|
3913
|
-
// removed in PR #406 (X1+G3) so the child's `output.error`
|
|
3914
|
-
// is the single source of provisioning failure.
|
|
3915
|
-
onDone: [
|
|
3916
|
-
{
|
|
3917
|
-
guard: "childReportedError",
|
|
3918
|
-
target: "error",
|
|
3919
|
-
actions: [
|
|
3920
|
-
"assignChildReportedError",
|
|
3921
|
-
"trackProvisioningFailed"
|
|
3922
|
-
]
|
|
3923
|
-
},
|
|
3924
|
-
{
|
|
3925
|
-
target: "complete",
|
|
3926
|
-
actions: [
|
|
3927
|
-
"assignAccountFromChild",
|
|
3928
|
-
"trackOnboardingCompleted"
|
|
3929
|
-
]
|
|
3930
|
-
}
|
|
3931
|
-
],
|
|
3932
|
-
// `onError` is the safety net for an unexpected throw from
|
|
3933
|
-
// inside the child machine itself (not the spawned actor's
|
|
3934
|
-
// `error` final state, which goes through `onDone`). In
|
|
3935
|
-
// normal flow this never fires.
|
|
3936
|
-
onError: {
|
|
3937
|
-
target: "error",
|
|
3938
|
-
actions: ["assignChildThrown", "trackProvisioningFailed"]
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
},
|
|
3942
|
-
// TODO(stack-1): wire `action_required` once the KYC gate /
|
|
3943
|
-
// async-resume `NextAction` path is lifted from
|
|
3944
|
-
// `useOperation(operationId)`. PROCEED → provisioning re-enters
|
|
3945
|
-
// the actor with the resumed input. Currently unreachable from
|
|
3946
|
-
// any transition; declared for parity with the public type.
|
|
3947
|
-
action_required: {
|
|
3948
|
-
on: {
|
|
3949
|
-
PROCEED: { target: "provisioning" },
|
|
3950
|
-
RESET: { target: "profile", actions: "resetContext" }
|
|
3951
|
-
}
|
|
3952
|
-
},
|
|
3953
|
-
complete: {
|
|
3954
|
-
on: {
|
|
3955
|
-
RESET: { target: "profile", actions: "resetContext" }
|
|
3956
|
-
}
|
|
3957
|
-
},
|
|
3958
|
-
error: {
|
|
3959
|
-
on: {
|
|
3960
|
-
RESET: { target: "profile", actions: "resetContext" }
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
});
|
|
3965
|
-
}
|
|
3966
|
-
function generateOperationId() {
|
|
3967
|
-
return toOperationId(`op_onboarding_local_${Date.now().toString(36)}`);
|
|
3968
|
-
}
|
|
3969
|
-
function requireProvisionInput2(context) {
|
|
3970
|
-
if (!context.input || !context.input.signerProvider) {
|
|
3971
|
-
throw Errors.invalidInput(
|
|
3972
|
-
"signerProvider",
|
|
3973
|
-
"Onboarding flow advanced to provisioning without a signerProvider in context."
|
|
3974
|
-
);
|
|
3975
|
-
}
|
|
3976
|
-
return {
|
|
3977
|
-
displayName: context.input.displayName,
|
|
3978
|
-
username: context.input.username,
|
|
3979
|
-
countryCode: context.input.country,
|
|
3980
|
-
signerProvider: context.input.signerProvider
|
|
3981
|
-
};
|
|
3982
|
-
}
|
|
3983
|
-
function extractChildOutput(event) {
|
|
3984
|
-
if (typeof event !== "object" || event === null || !("output" in event)) {
|
|
3985
|
-
return null;
|
|
3986
|
-
}
|
|
3987
|
-
const output = event.output;
|
|
3988
|
-
if (typeof output !== "object" || output === null) return null;
|
|
3989
|
-
return output;
|
|
3990
|
-
}
|
|
3991
|
-
function extractChildErrorOrFallback(event) {
|
|
3992
|
-
const output = extractChildOutput(event);
|
|
3993
|
-
if (output && "error" in output && output.error) return output.error;
|
|
3994
|
-
return Errors.providerError(
|
|
3995
|
-
"onboarding",
|
|
3996
|
-
"flow",
|
|
3997
|
-
new Error("provisioning child reported error without payload")
|
|
3998
|
-
);
|
|
3999
|
-
}
|
|
4000
|
-
function extractChildAccountOrNull(event) {
|
|
4001
|
-
const output = extractChildOutput(event);
|
|
4002
|
-
if (output && "account" in output && output.account) return output.account;
|
|
4003
|
-
return null;
|
|
4004
|
-
}
|
|
4005
|
-
function errorFromEvent2(event) {
|
|
4006
|
-
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
4007
|
-
if (cause instanceof CapxulError) return cause;
|
|
4008
|
-
if (cause instanceof CapxulError2) return cause;
|
|
4009
|
-
return Errors.providerError("onboarding", "flow", cause);
|
|
4010
|
-
}
|
|
4011
|
-
|
|
4012
|
-
// src/client.ts
|
|
4013
|
-
function createCapxulClient(config = {}) {
|
|
4014
|
-
const clientWithoutFlows = {
|
|
4015
|
-
id: crypto.randomUUID(),
|
|
4016
|
-
auth: new AuthService(config),
|
|
4017
|
-
me: createMeClient(config),
|
|
4018
|
-
accounts: createAccountsClient(config),
|
|
4019
|
-
organizations: createOrganizationsClient(config),
|
|
4020
|
-
payments: createPaymentsClient(config),
|
|
4021
|
-
transfers: createTransfersClient(),
|
|
4022
|
-
tokenTransfers: createTokenTransfersClient(config),
|
|
4023
|
-
withdrawals: createWithdrawalsClient(config),
|
|
4024
|
-
documents: createDocumentsClient(config),
|
|
4025
|
-
subAccounts: createSubAccountsClient(config),
|
|
4026
|
-
virtualAccounts: createVirtualAccountsClient(),
|
|
4027
|
-
virtualCards: createVirtualCardsClient(),
|
|
4028
|
-
externalAccounts: createExternalAccountsClient(config),
|
|
4029
|
-
operations: createOperationsClient(config),
|
|
4030
|
-
webhookEndpoints: createWebhookEndpointsClient(),
|
|
4031
|
-
webhookEvents: createWebhookEventsClient(),
|
|
4032
|
-
apiKeys: createApiKeysClient()
|
|
4033
|
-
};
|
|
4034
|
-
const client = clientWithoutFlows;
|
|
4035
|
-
client.flows = {
|
|
4036
|
-
onboarding: () => createOnboardingFlowMachine(client),
|
|
4037
|
-
provisioning: () => createProvisioningMachine(client)
|
|
4038
|
-
};
|
|
4039
|
-
return client;
|
|
4040
|
-
}
|
|
4041
|
-
|
|
4042
|
-
exports.createCapxulClient = createCapxulClient;
|