@capxul/sdk 0.2.0-alpha.3 → 0.2.0-alpha.4

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/dist/client.cjs CHANGED
@@ -5,7 +5,6 @@ var accounts = require('permissionless/accounts');
5
5
  var viem = require('viem');
6
6
  var accountAbstraction = require('viem/account-abstraction');
7
7
  var chains = require('viem/chains');
8
- var safeDerive = require('@repo/safe-derive');
9
8
  var browser = require('convex/browser');
10
9
  var xstate = require('xstate');
11
10
 
@@ -111,18 +110,6 @@ function track(...args) {
111
110
  const [name, props] = args;
112
111
  debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
113
112
  }
114
- function formatDebugValue2(value) {
115
- if (value === void 0 || value === "") return "";
116
- if (typeof value === "string") return value;
117
- try {
118
- return JSON.stringify(value);
119
- } catch {
120
- return String(value);
121
- }
122
- }
123
- function identify(userId, traits) {
124
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
125
- }
126
113
 
127
114
  // ../config/src/chain.ts
128
115
  var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
@@ -232,6 +219,7 @@ var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
232
219
  var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
233
220
  var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
234
221
  var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
222
+ var MULTI_SEND = "0x38869bf66a61cf6bdb996a6ae40d5853fd43b526";
235
223
 
236
224
  // ../config/src/org-roles.ts
237
225
  function roleKeyFromLabel(label) {
@@ -242,6 +230,117 @@ function roleKeyFromLabel(label) {
242
230
  roleKeyFromLabel("OWNER");
243
231
  roleKeyFromLabel("FINANCE_MANAGER");
244
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
245
344
  async function buildSafeAccount(signer, chain) {
246
345
  try {
247
346
  const publicClient = viem.createPublicClient({
@@ -253,7 +352,7 @@ async function buildSafeAccount(signer, chain) {
253
352
  entryPoint: { address: accountAbstraction.entryPoint07Address, version: "0.7" },
254
353
  version: "1.4.1",
255
354
  owners: [signer],
256
- saltNonce: computeSaltNonce(signer.address),
355
+ saltNonce: computeSaltNonce2(signer.address),
257
356
  safeSingletonAddress: SAFE_L2_SINGLETON,
258
357
  safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
259
358
  safeModuleSetupAddress: SAFE_MODULE_SETUP,
@@ -270,11 +369,11 @@ async function buildSafeAccount(signer, chain) {
270
369
  });
271
370
  }
272
371
  }
273
- function computeSaltNonce(ownerAddress) {
372
+ function computeSaltNonce2(ownerAddress) {
274
373
  return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
275
374
  }
276
- function deriveSafeAddress(signerAddress) {
277
- return safeDerive.deriveSafeAddress(signerAddress, safeDerive.defaultSafeDeriveConfig);
375
+ function deriveSafeAddress2(signerAddress) {
376
+ return deriveSafeAddress(signerAddress, defaultSafeDeriveConfig);
278
377
  }
279
378
 
280
379
  // ../platform-kernel/src/ids.ts
@@ -930,7 +1029,7 @@ function createAccountsClient(config = {}) {
930
1029
  username: input.username,
931
1030
  countryCode: input.countryCode,
932
1031
  eoaAddress: input.signerProvider.signerAddress,
933
- safeAddress: deriveSafeAddress(input.signerProvider.signerAddress)
1032
+ safeAddress: deriveSafeAddress2(input.signerProvider.signerAddress)
934
1033
  }
935
1034
  );
936
1035
  const account = await config._data.query(
@@ -1516,8 +1615,17 @@ function createAuthClient(config = {}) {
1516
1615
  ];
1517
1616
  }
1518
1617
  try {
1519
- const safeAddress = deriveSafeAddress(signerAddress);
1520
- const result = await data.mutation(api.authBootstrap.completeBootstrap, {
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, {
1521
1629
  bootstrapToken: input.bootstrapToken,
1522
1630
  sessionToken: session.token,
1523
1631
  username: input.username,
@@ -1700,16 +1808,227 @@ function mutableConfig(config) {
1700
1808
  return config;
1701
1809
  }
1702
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
+
1703
1893
  // src/core/documents.ts
1704
- function createDocumentsClient() {
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;
1705
1905
  return {
1706
- create: async () => stub("documents.create"),
1707
- retrieve: async () => stub("documents.retrieve"),
1708
- list: async () => stub("documents.list"),
1709
- cancel: async () => stub("documents.cancel")
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()
1710
1924
  };
1711
1925
  }
1712
- function createOrgDocumentsClient() {
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 = {}) {
1713
2032
  return {
1714
2033
  create: async () => stub("organizations.documents.create"),
1715
2034
  retrieve: async () => stub("organizations.documents.retrieve"),
@@ -3037,7 +3356,9 @@ function createOrganizationsClient(config = {}) {
3037
3356
  },
3038
3357
  invite: async (input) => {
3039
3358
  if (!config._data?.action) {
3040
- return stub("organizations.members.invite");
3359
+ return stub(
3360
+ "organizations.members.invite"
3361
+ );
3041
3362
  }
3042
3363
  try {
3043
3364
  const orgId = input.organizationId.replace(/^org_/, "");
@@ -3125,7 +3446,9 @@ function createOrganizationsClient(config = {}) {
3125
3446
  },
3126
3447
  resend: async (input) => {
3127
3448
  if (!config._data?.action) {
3128
- return stub("organizations.members.resend");
3449
+ return stub(
3450
+ "organizations.members.resend"
3451
+ );
3129
3452
  }
3130
3453
  try {
3131
3454
  const orgId = input.organizationId.replace(/^org_/, "");
@@ -3193,7 +3516,7 @@ function createOrganizationsClient(config = {}) {
3193
3516
  payments: createOrgPaymentsClient(),
3194
3517
  transfers: createOrgTransfersClient(),
3195
3518
  withdrawals: createOrgWithdrawalsClient(config),
3196
- documents: createOrgDocumentsClient(),
3519
+ documents: createOrgDocumentsClient(config),
3197
3520
  webhookEndpoints: createWebhookEndpointsClient(),
3198
3521
  webhookEvents: createWebhookEventsClient()
3199
3522
  };
@@ -3310,609 +3633,6 @@ function createVirtualCardsClient() {
3310
3633
  cancel: async () => stub("virtualCards.cancel")
3311
3634
  };
3312
3635
  }
3313
- function createAuthFlowMachine(client) {
3314
- return xstate.setup({
3315
- types: {},
3316
- actors: {
3317
- // XState v5's `fromPromise` injects an `AbortSignal` that aborts
3318
- // when the actor is stopped (parent transition fires, machine is
3319
- // disposed, etc.). Plumbing it through `client.auth.sendOtp` /
3320
- // `client.auth.verifyOtp` makes the in-flight HTTP request
3321
- // cancellable: stale responses can't race a state machine
3322
- // that's already moved on. See PR #406 S5.
3323
- sendOtp: xstate.fromPromise(async ({ input, signal }) => {
3324
- const [error] = await client.auth.sendOtp(
3325
- { email: input.email },
3326
- { signal }
3327
- );
3328
- if (error) throw error;
3329
- }),
3330
- verifyOtp: xstate.fromPromise(
3331
- async ({ input, signal }) => {
3332
- const [error, result] = await client.auth.verifyOtp(
3333
- {
3334
- email: input.email,
3335
- otp: input.code
3336
- },
3337
- { signal }
3338
- );
3339
- if (error) throw error;
3340
- if (result.kind === "bootstrap_required") {
3341
- throw new CapxulError({
3342
- code: "ACTION_REQUIRED",
3343
- message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
3344
- details: { reason: result.reason }
3345
- });
3346
- }
3347
- return result.session;
3348
- }
3349
- ),
3350
- signOut: xstate.fromPromise(async () => {
3351
- const [error] = await client.auth.signOut();
3352
- if (error) throw error;
3353
- })
3354
- },
3355
- actions: {
3356
- trackOtpRequested: ({ context }) => {
3357
- if (!context.email) return;
3358
- track("auth_otp_requested", {
3359
- email_domain: emailDomain(context.email)
3360
- });
3361
- },
3362
- trackOtpFailed: ({ event }) => {
3363
- const error = errorFromEvent(event);
3364
- track("auth_failed", {
3365
- auth_type: "email_otp",
3366
- reason: error.code
3367
- });
3368
- },
3369
- trackTimeoutFailed: () => {
3370
- track("auth_failed", {
3371
- auth_type: "email_otp",
3372
- reason: "timeout"
3373
- });
3374
- },
3375
- trackVerified: () => {
3376
- track("auth_verified", { auth_type: "email_otp" });
3377
- },
3378
- identifyAndTrack: ({ context }) => {
3379
- if (!context.session) return;
3380
- identify(context.session.authUserId, {
3381
- email_domain: emailDomain(context.session.email)
3382
- });
3383
- track("auth_identified", {
3384
- email_domain: emailDomain(context.session.email)
3385
- });
3386
- },
3387
- trackSignedOut: () => {
3388
- track("auth_signed_out");
3389
- }
3390
- }
3391
- }).createMachine({
3392
- id: "auth",
3393
- initial: "idle",
3394
- context: { email: null, session: null, error: null },
3395
- states: {
3396
- idle: {
3397
- on: {
3398
- REQUEST_OTP: {
3399
- target: "sending_otp",
3400
- actions: xstate.assign({
3401
- email: ({ event }) => event.email,
3402
- error: () => null
3403
- })
3404
- }
3405
- }
3406
- },
3407
- sending_otp: {
3408
- invoke: {
3409
- src: "sendOtp",
3410
- input: ({ context }) => ({ email: requireEmail(context) }),
3411
- onDone: {
3412
- target: "otp_requested",
3413
- actions: ["trackOtpRequested"]
3414
- },
3415
- onError: {
3416
- target: "error",
3417
- actions: [
3418
- xstate.assign({ error: ({ event }) => errorFromEvent(event) }),
3419
- "trackOtpFailed"
3420
- ]
3421
- }
3422
- },
3423
- after: {
3424
- [FLOW_INVOKE_TIMEOUT_MS]: {
3425
- target: "error",
3426
- actions: [
3427
- xstate.assign({
3428
- error: () => timeoutError("sending_otp")
3429
- }),
3430
- "trackTimeoutFailed"
3431
- ]
3432
- }
3433
- }
3434
- },
3435
- otp_requested: {
3436
- on: {
3437
- VERIFY: { target: "verifying" },
3438
- RESET: {
3439
- target: "idle",
3440
- actions: xstate.assign({ email: () => null, error: () => null })
3441
- }
3442
- }
3443
- },
3444
- verifying: {
3445
- invoke: {
3446
- src: "verifyOtp",
3447
- input: ({ context, event }) => ({
3448
- email: requireEmail(context),
3449
- code: requireCodeFromEvent(event)
3450
- }),
3451
- onDone: {
3452
- target: "authenticated",
3453
- actions: [
3454
- // Scrub the duplicate `context.email` (input value
3455
- // captured during sendOtp) since the verified
3456
- // `session.email` is now the canonical source
3457
- // post-authentication. The session's email is
3458
- // intentionally retained — it's the auth result, not
3459
- // lingering input. See PR #406 S2.
3460
- xstate.assign({
3461
- session: ({ event }) => event.output,
3462
- email: () => null
3463
- }),
3464
- "trackVerified",
3465
- "identifyAndTrack"
3466
- ]
3467
- },
3468
- onError: {
3469
- target: "error",
3470
- actions: [
3471
- xstate.assign({ error: ({ event }) => errorFromEvent(event) }),
3472
- "trackOtpFailed"
3473
- ]
3474
- }
3475
- },
3476
- after: {
3477
- [FLOW_INVOKE_TIMEOUT_MS]: {
3478
- target: "error",
3479
- actions: [
3480
- xstate.assign({
3481
- error: () => timeoutError("verifying")
3482
- }),
3483
- "trackTimeoutFailed"
3484
- ]
3485
- }
3486
- }
3487
- },
3488
- authenticated: {
3489
- on: {
3490
- SIGN_OUT: { target: "signing_out" }
3491
- }
3492
- },
3493
- signing_out: {
3494
- invoke: {
3495
- src: "signOut",
3496
- onDone: {
3497
- target: "idle",
3498
- actions: [
3499
- xstate.assign({
3500
- session: () => null,
3501
- email: () => null,
3502
- error: () => null
3503
- }),
3504
- "trackSignedOut"
3505
- ]
3506
- },
3507
- onError: {
3508
- target: "error",
3509
- actions: xstate.assign({ error: ({ event }) => errorFromEvent(event) })
3510
- }
3511
- }
3512
- },
3513
- error: {
3514
- on: {
3515
- RESET: {
3516
- target: "idle",
3517
- actions: xstate.assign({ error: () => null })
3518
- }
3519
- }
3520
- }
3521
- }
3522
- });
3523
- }
3524
- function requireEmail(context) {
3525
- if (!context.email) {
3526
- throw Errors.invalidInput(
3527
- "email",
3528
- "Auth flow advanced without an email captured in context."
3529
- );
3530
- }
3531
- return context.email;
3532
- }
3533
- function requireCodeFromEvent(event) {
3534
- if (event.type !== "VERIFY") {
3535
- throw Errors.invalidInput(
3536
- "code",
3537
- `Auth flow's verifying state requires a VERIFY event, got ${event.type}.`
3538
- );
3539
- }
3540
- return event.code;
3541
- }
3542
- function errorFromEvent(event) {
3543
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3544
- if (cause instanceof CapxulError) {
3545
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3546
- return new CapxulError({
3547
- code: cause.code,
3548
- message: redactEmail(cause.message),
3549
- cause,
3550
- details: cause.details,
3551
- operationId: cause.operationId,
3552
- correlationId: cause.correlationId,
3553
- retryable: cause.retryable
3554
- });
3555
- }
3556
- if (cause instanceof CapxulError2) {
3557
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3558
- return new CapxulError2(cause.code, redactEmail(cause.message), {
3559
- cause,
3560
- details: cause.details,
3561
- correlationId: cause.correlationId,
3562
- layer: cause.layer
3563
- });
3564
- }
3565
- return Errors.providerError("auth", "flow", redactCauseEmail(cause));
3566
- }
3567
- var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
3568
- var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
3569
- function redactEmail(message) {
3570
- return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
3571
- }
3572
- function redactCauseEmail(cause) {
3573
- if (cause instanceof Error) {
3574
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3575
- const redacted = new Error(redactEmail(cause.message));
3576
- redacted.cause = cause;
3577
- return redacted;
3578
- }
3579
- if (typeof cause === "string") {
3580
- return redactEmail(cause);
3581
- }
3582
- return cause;
3583
- }
3584
- function timeoutError(state) {
3585
- return Errors.providerError(
3586
- "auth",
3587
- "flow",
3588
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3589
- );
3590
- }
3591
- function emailDomain(email) {
3592
- const domain = email.split("@")[1]?.trim().toLowerCase();
3593
- return domain || "unknown";
3594
- }
3595
- var initialContext = {
3596
- email: null,
3597
- code: null,
3598
- username: null,
3599
- bootstrapToken: null,
3600
- bootstrapReason: null,
3601
- session: null,
3602
- account: null,
3603
- safe: null,
3604
- error: null
3605
- };
3606
- function createAuthBootstrapFlowMachine(client) {
3607
- return xstate.setup({
3608
- types: {},
3609
- actors: {
3610
- sendOtp: xstate.fromPromise(async ({ input, signal }) => {
3611
- const [error] = await client.auth.sendOtp(
3612
- { email: input.email },
3613
- { signal }
3614
- );
3615
- if (error) throw error;
3616
- }),
3617
- verifyOtp: xstate.fromPromise(
3618
- async ({ input, signal }) => {
3619
- const [error, result] = await client.auth.verifyOtp(
3620
- { email: input.email, otp: input.code },
3621
- { signal }
3622
- );
3623
- if (error) throw error;
3624
- return result;
3625
- }
3626
- ),
3627
- completeBootstrap: xstate.fromPromise(async ({ input }) => {
3628
- const [error, result] = await client.auth.completeBootstrap(input);
3629
- if (error) throw error;
3630
- return result;
3631
- }),
3632
- signOut: xstate.fromPromise(async () => {
3633
- const [error] = await client.auth.signOut();
3634
- if (error) throw error;
3635
- })
3636
- },
3637
- actions: {
3638
- trackOtpRequested: ({ context }) => {
3639
- if (!context.email) return;
3640
- track("auth_otp_requested", {
3641
- email_domain: emailDomain2(context.email)
3642
- });
3643
- },
3644
- trackFailed: ({ event }) => {
3645
- track("auth_failed", {
3646
- auth_type: "email_otp",
3647
- reason: errorFromEvent2(event).code
3648
- });
3649
- },
3650
- trackTimeoutFailed: () => {
3651
- track("auth_failed", {
3652
- auth_type: "email_otp",
3653
- reason: "timeout"
3654
- });
3655
- },
3656
- trackVerified: () => {
3657
- track("auth_verified", { auth_type: "email_otp" });
3658
- },
3659
- trackBootstrapRequired: ({ context }) => {
3660
- track("auth_verified", {
3661
- auth_type: "email_otp",
3662
- auth_mode: context.bootstrapReason ?? "bootstrap_required"
3663
- });
3664
- },
3665
- identifyAndTrack: ({ context }) => {
3666
- if (!context.session) return;
3667
- identify(context.session.authUserId, {
3668
- email_domain: emailDomain2(context.session.email)
3669
- });
3670
- track("auth_identified", {
3671
- email_domain: emailDomain2(context.session.email)
3672
- });
3673
- },
3674
- trackSignedOut: () => {
3675
- track("auth_signed_out");
3676
- }
3677
- }
3678
- }).createMachine({
3679
- id: "authBootstrap",
3680
- initial: "email",
3681
- context: initialContext,
3682
- states: {
3683
- email: {
3684
- on: {
3685
- ENTER_EMAIL: {
3686
- actions: xstate.assign({
3687
- email: ({ event }) => event.email,
3688
- error: () => null
3689
- })
3690
- },
3691
- REQUEST_OTP: { target: "sending_otp" }
3692
- }
3693
- },
3694
- sending_otp: {
3695
- invoke: {
3696
- src: "sendOtp",
3697
- input: ({ context }) => ({ email: requireEmail2(context) }),
3698
- onDone: { target: "otp_requested", actions: "trackOtpRequested" },
3699
- onError: {
3700
- target: "otp_requested",
3701
- actions: [
3702
- xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3703
- "trackFailed"
3704
- ]
3705
- }
3706
- },
3707
- after: {
3708
- [FLOW_INVOKE_TIMEOUT_MS]: {
3709
- target: "otp_requested",
3710
- actions: [
3711
- xstate.assign({ error: () => timeoutError2("sending_otp") }),
3712
- "trackTimeoutFailed"
3713
- ]
3714
- }
3715
- }
3716
- },
3717
- otp_requested: {
3718
- on: {
3719
- ENTER_OTP: {
3720
- actions: xstate.assign({
3721
- code: ({ event }) => event.code,
3722
- error: () => null
3723
- })
3724
- },
3725
- VERIFY_OTP: { target: "verifying_otp" },
3726
- BACK: { target: "email" },
3727
- RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3728
- }
3729
- },
3730
- verifying_otp: {
3731
- invoke: {
3732
- src: "verifyOtp",
3733
- input: ({ context }) => ({
3734
- email: requireEmail2(context),
3735
- code: requireCode(context)
3736
- }),
3737
- onDone: [
3738
- {
3739
- guard: ({ event }) => event.output.kind === "existing_member",
3740
- target: "authenticated",
3741
- actions: [
3742
- xstate.assign({
3743
- session: ({ event }) => event.output.session,
3744
- account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
3745
- username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
3746
- safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
3747
- email: () => null,
3748
- error: () => null
3749
- }),
3750
- "trackVerified",
3751
- "identifyAndTrack"
3752
- ]
3753
- },
3754
- {
3755
- target: "bootstrap_required",
3756
- actions: [
3757
- xstate.assign({
3758
- session: ({ event }) => event.output.session,
3759
- bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
3760
- bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
3761
- username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
3762
- email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
3763
- error: () => null
3764
- }),
3765
- "trackVerified",
3766
- "trackBootstrapRequired"
3767
- ]
3768
- }
3769
- ],
3770
- onError: {
3771
- target: "otp_requested",
3772
- actions: [
3773
- xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3774
- "trackFailed"
3775
- ]
3776
- }
3777
- },
3778
- after: {
3779
- [FLOW_INVOKE_TIMEOUT_MS]: {
3780
- target: "otp_requested",
3781
- actions: [
3782
- xstate.assign({ error: () => timeoutError2("verifying_otp") }),
3783
- "trackTimeoutFailed"
3784
- ]
3785
- }
3786
- }
3787
- },
3788
- bootstrap_required: {
3789
- on: {
3790
- ENTER_USERNAME: {
3791
- actions: xstate.assign({
3792
- username: ({ event }) => event.username,
3793
- error: () => null
3794
- })
3795
- },
3796
- COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3797
- BACK: { target: "otp_requested" },
3798
- RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3799
- }
3800
- },
3801
- completing_bootstrap: {
3802
- invoke: {
3803
- src: "completeBootstrap",
3804
- input: ({ context }) => ({
3805
- bootstrapToken: requireBootstrapToken(context),
3806
- username: requireUsername(context)
3807
- }),
3808
- onDone: {
3809
- target: "authenticated",
3810
- actions: [
3811
- xstate.assign({
3812
- session: ({ event }) => event.output.session,
3813
- account: ({ event }) => event.output.account,
3814
- username: ({ event }) => event.output.username,
3815
- safe: ({ event }) => event.output.safe,
3816
- bootstrapToken: () => null,
3817
- bootstrapReason: () => null,
3818
- email: () => null,
3819
- error: () => null
3820
- }),
3821
- "identifyAndTrack"
3822
- ]
3823
- },
3824
- onError: {
3825
- target: "bootstrap_required",
3826
- actions: [
3827
- xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3828
- "trackFailed"
3829
- ]
3830
- }
3831
- },
3832
- after: {
3833
- [FLOW_INVOKE_TIMEOUT_MS]: {
3834
- target: "bootstrap_required",
3835
- actions: [
3836
- xstate.assign({ error: () => timeoutError2("completing_bootstrap") }),
3837
- "trackTimeoutFailed"
3838
- ]
3839
- }
3840
- }
3841
- },
3842
- authenticated: {
3843
- on: {
3844
- SIGN_OUT: { target: "signing_out" }
3845
- }
3846
- },
3847
- signing_out: {
3848
- invoke: {
3849
- src: "signOut",
3850
- onDone: {
3851
- target: "email",
3852
- actions: [
3853
- xstate.assign(() => initialContext),
3854
- "trackSignedOut"
3855
- ]
3856
- },
3857
- onError: {
3858
- target: "error",
3859
- actions: xstate.assign({ error: ({ event }) => errorFromEvent2(event) })
3860
- }
3861
- }
3862
- },
3863
- error: {
3864
- on: {
3865
- RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3866
- }
3867
- }
3868
- }
3869
- });
3870
- }
3871
- function requireEmail2(context) {
3872
- if (!context.email) {
3873
- throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3874
- }
3875
- return context.email;
3876
- }
3877
- function requireCode(context) {
3878
- if (!context.code) {
3879
- throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3880
- }
3881
- return context.code;
3882
- }
3883
- function requireBootstrapToken(context) {
3884
- if (!context.bootstrapToken) {
3885
- throw Errors.invalidInput(
3886
- "bootstrapToken",
3887
- "Auth bootstrap requires a continuation token."
3888
- );
3889
- }
3890
- return context.bootstrapToken;
3891
- }
3892
- function requireUsername(context) {
3893
- if (!context.username) {
3894
- throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3895
- }
3896
- return context.username;
3897
- }
3898
- function errorFromEvent2(event) {
3899
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3900
- if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3901
- return cause;
3902
- }
3903
- return Errors.providerError("auth", "bootstrap", cause);
3904
- }
3905
- function timeoutError2(state) {
3906
- return Errors.providerError(
3907
- "auth",
3908
- "bootstrap",
3909
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3910
- );
3911
- }
3912
- function emailDomain2(email) {
3913
- const domain = email.split("@")[1]?.trim().toLowerCase();
3914
- return domain || "unknown";
3915
- }
3916
3636
  function createProvisioningMachine(client) {
3917
3637
  return xstate.setup({
3918
3638
  types: {},
@@ -3940,7 +3660,7 @@ function createProvisioningMachine(client) {
3940
3660
  const provider = context.input?.signerProvider;
3941
3661
  if (!provider) return;
3942
3662
  track("provisioning_safe_created", {
3943
- safe_address: deriveSafeAddress(provider.signerAddress)
3663
+ safe_address: deriveSafeAddress2(provider.signerAddress)
3944
3664
  });
3945
3665
  }
3946
3666
  }
@@ -3985,13 +3705,13 @@ function createProvisioningMachine(client) {
3985
3705
  },
3986
3706
  onError: {
3987
3707
  target: "error",
3988
- actions: xstate.assign({ error: ({ event }) => errorFromEvent3(event) })
3708
+ actions: xstate.assign({ error: ({ event }) => errorFromEvent(event) })
3989
3709
  }
3990
3710
  },
3991
3711
  after: {
3992
3712
  [FLOW_INVOKE_TIMEOUT_MS]: {
3993
3713
  target: "error",
3994
- actions: xstate.assign({ error: () => timeoutError3() })
3714
+ actions: xstate.assign({ error: () => timeoutError() })
3995
3715
  }
3996
3716
  }
3997
3717
  },
@@ -4009,7 +3729,7 @@ function createProvisioningMachine(client) {
4009
3729
  * this payload on its `onDone` transition and branches via guards
4010
3730
  * on `event.output.error`.
4011
3731
  */
4012
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
3732
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError() }
4013
3733
  });
4014
3734
  }
4015
3735
  function requireProvisionInput(context) {
@@ -4021,13 +3741,13 @@ function requireProvisionInput(context) {
4021
3741
  }
4022
3742
  return context.input;
4023
3743
  }
4024
- function errorFromEvent3(event) {
3744
+ function errorFromEvent(event) {
4025
3745
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4026
3746
  if (cause instanceof CapxulError) return cause;
4027
3747
  if (cause instanceof CapxulError2) return cause;
4028
3748
  return Errors.providerError("provisioning", "flow", cause);
4029
3749
  }
4030
- function timeoutError3() {
3750
+ function timeoutError() {
4031
3751
  return Errors.providerError(
4032
3752
  "provisioning",
4033
3753
  "flow",
@@ -4120,7 +3840,7 @@ function createOnboardingFlowMachine(client) {
4120
3840
  error: ({ event }) => extractChildErrorOrFallback(event)
4121
3841
  }),
4122
3842
  assignChildThrown: xstate.assign({
4123
- error: ({ event }) => errorFromEvent4(event)
3843
+ error: ({ event }) => errorFromEvent2(event)
4124
3844
  }),
4125
3845
  assignAccountFromChild: xstate.assign({
4126
3846
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -4282,7 +4002,7 @@ function extractChildAccountOrNull(event) {
4282
4002
  if (output && "account" in output && output.account) return output.account;
4283
4003
  return null;
4284
4004
  }
4285
- function errorFromEvent4(event) {
4005
+ function errorFromEvent2(event) {
4286
4006
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4287
4007
  if (cause instanceof CapxulError) return cause;
4288
4008
  if (cause instanceof CapxulError2) return cause;
@@ -4293,7 +4013,7 @@ function errorFromEvent4(event) {
4293
4013
  function createCapxulClient(config = {}) {
4294
4014
  const clientWithoutFlows = {
4295
4015
  id: crypto.randomUUID(),
4296
- auth: createAuthClient(config),
4016
+ auth: new AuthService(config),
4297
4017
  me: createMeClient(config),
4298
4018
  accounts: createAccountsClient(config),
4299
4019
  organizations: createOrganizationsClient(config),
@@ -4301,7 +4021,7 @@ function createCapxulClient(config = {}) {
4301
4021
  transfers: createTransfersClient(),
4302
4022
  tokenTransfers: createTokenTransfersClient(config),
4303
4023
  withdrawals: createWithdrawalsClient(config),
4304
- documents: createDocumentsClient(),
4024
+ documents: createDocumentsClient(config),
4305
4025
  subAccounts: createSubAccountsClient(config),
4306
4026
  virtualAccounts: createVirtualAccountsClient(),
4307
4027
  virtualCards: createVirtualCardsClient(),
@@ -4313,8 +4033,6 @@ function createCapxulClient(config = {}) {
4313
4033
  };
4314
4034
  const client = clientWithoutFlows;
4315
4035
  client.flows = {
4316
- auth: () => createAuthFlowMachine(client),
4317
- authBootstrap: () => createAuthBootstrapFlowMachine(client),
4318
4036
  onboarding: () => createOnboardingFlowMachine(client),
4319
4037
  provisioning: () => createProvisioningMachine(client)
4320
4038
  };