@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.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { componentsGeneric, anyApi } from 'convex/server';
2
2
  import { toSafeSmartAccount } from 'permissionless/accounts';
3
- import { getAddress, parseUnits, createPublicClient, http, encodeFunctionData } from 'viem';
3
+ import { getAddress, parseUnits, encodeFunctionData, encodePacked, keccak256, getContractAddress, createPublicClient, http } from 'viem';
4
4
  import { entryPoint07Address, createPaymasterClient, createBundlerClient } from 'viem/account-abstraction';
5
5
  import { baseSepolia } from 'viem/chains';
6
- import { deriveSafeAddress as deriveSafeAddress$1, defaultSafeDeriveConfig } from '@repo/safe-derive';
7
6
  import { ConvexHttpClient } from 'convex/browser';
8
7
  import { setup, fromPromise, assign } from 'xstate';
9
8
 
@@ -109,18 +108,6 @@ function track(...args) {
109
108
  const [name, props] = args;
110
109
  debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
111
110
  }
112
- function formatDebugValue2(value) {
113
- if (value === void 0 || value === "") return "";
114
- if (typeof value === "string") return value;
115
- try {
116
- return JSON.stringify(value);
117
- } catch {
118
- return String(value);
119
- }
120
- }
121
- function identify(userId, traits) {
122
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
123
- }
124
111
 
125
112
  // ../config/src/chain.ts
126
113
  var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
@@ -230,6 +217,7 @@ var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
230
217
  var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
231
218
  var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
232
219
  var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
220
+ var MULTI_SEND = "0x38869bf66a61cf6bdb996a6ae40d5853fd43b526";
233
221
 
234
222
  // ../config/src/org-roles.ts
235
223
  function roleKeyFromLabel(label) {
@@ -240,6 +228,117 @@ function roleKeyFromLabel(label) {
240
228
  roleKeyFromLabel("OWNER");
241
229
  roleKeyFromLabel("FINANCE_MANAGER");
242
230
  roleKeyFromLabel("PAYMENTS_OPERATOR");
231
+ var defaultSafeDeriveConfig = {
232
+ safeProxyFactory: SAFE_PROXY_FACTORY,
233
+ safeL2Singleton: SAFE_L2_SINGLETON,
234
+ safeModuleSetup: SAFE_MODULE_SETUP,
235
+ safe4337Module: SAFE_4337_MODULE,
236
+ multiSend: MULTI_SEND
237
+ };
238
+ var SAFE_PROXY_CREATION_CODE = "0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564";
239
+ var enableModulesAbi = [
240
+ {
241
+ type: "function",
242
+ name: "enableModules",
243
+ inputs: [{ type: "address[]", name: "modules" }],
244
+ outputs: [],
245
+ stateMutability: "nonpayable"
246
+ }
247
+ ];
248
+ var multiSendAbi = [
249
+ {
250
+ type: "function",
251
+ name: "multiSend",
252
+ inputs: [{ type: "bytes", name: "transactions" }],
253
+ outputs: [],
254
+ stateMutability: "payable"
255
+ }
256
+ ];
257
+ var setupAbi = [
258
+ {
259
+ type: "function",
260
+ name: "setup",
261
+ inputs: [
262
+ { type: "address[]", name: "owners" },
263
+ { type: "uint256", name: "threshold" },
264
+ { type: "address", name: "to" },
265
+ { type: "bytes", name: "data" },
266
+ { type: "address", name: "fallbackHandler" },
267
+ { type: "address", name: "paymentToken" },
268
+ { type: "uint256", name: "payment" },
269
+ { type: "address", name: "paymentReceiver" }
270
+ ],
271
+ outputs: [],
272
+ stateMutability: "nonpayable"
273
+ }
274
+ ];
275
+ function encodeInternalTransaction(tx) {
276
+ const encoded = encodePacked(
277
+ ["uint8", "address", "uint256", "uint256", "bytes"],
278
+ [
279
+ tx.operation,
280
+ tx.to,
281
+ tx.value,
282
+ BigInt(tx.data.slice(2).length / 2),
283
+ tx.data
284
+ ]
285
+ );
286
+ return encoded.slice(2);
287
+ }
288
+ function computeSaltNonce(ownerAddress) {
289
+ return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
290
+ }
291
+ function deriveSafeAddress(signerAddress, config = defaultSafeDeriveConfig) {
292
+ const saltNonce = computeSaltNonce(signerAddress);
293
+ const enableModulesData = encodeFunctionData({
294
+ abi: enableModulesAbi,
295
+ functionName: "enableModules",
296
+ args: [[config.safe4337Module]]
297
+ });
298
+ const innerTx = encodeInternalTransaction({
299
+ operation: 1,
300
+ to: config.safeModuleSetup,
301
+ value: 0n,
302
+ data: enableModulesData
303
+ });
304
+ const multiSendCallData = encodeFunctionData({
305
+ abi: multiSendAbi,
306
+ functionName: "multiSend",
307
+ args: [`0x${innerTx}`]
308
+ });
309
+ const initializer = encodeFunctionData({
310
+ abi: setupAbi,
311
+ functionName: "setup",
312
+ args: [
313
+ [signerAddress],
314
+ 1n,
315
+ config.multiSend,
316
+ multiSendCallData,
317
+ config.safe4337Module,
318
+ "0x0000000000000000000000000000000000000000",
319
+ 0n,
320
+ "0x0000000000000000000000000000000000000000"
321
+ ]
322
+ });
323
+ const deploymentCode = encodePacked(
324
+ ["bytes", "uint256"],
325
+ [SAFE_PROXY_CREATION_CODE, BigInt(config.safeL2Singleton)]
326
+ );
327
+ const salt = keccak256(
328
+ encodePacked(
329
+ ["bytes32", "uint256"],
330
+ [keccak256(encodePacked(["bytes"], [initializer])), saltNonce]
331
+ )
332
+ );
333
+ return getContractAddress({
334
+ from: config.safeProxyFactory,
335
+ salt,
336
+ bytecode: deploymentCode,
337
+ opcode: "CREATE2"
338
+ });
339
+ }
340
+
341
+ // src/internal/safe/account.ts
243
342
  async function buildSafeAccount(signer, chain) {
244
343
  try {
245
344
  const publicClient = createPublicClient({
@@ -251,7 +350,7 @@ async function buildSafeAccount(signer, chain) {
251
350
  entryPoint: { address: entryPoint07Address, version: "0.7" },
252
351
  version: "1.4.1",
253
352
  owners: [signer],
254
- saltNonce: computeSaltNonce(signer.address),
353
+ saltNonce: computeSaltNonce2(signer.address),
255
354
  safeSingletonAddress: SAFE_L2_SINGLETON,
256
355
  safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
257
356
  safeModuleSetupAddress: SAFE_MODULE_SETUP,
@@ -268,11 +367,11 @@ async function buildSafeAccount(signer, chain) {
268
367
  });
269
368
  }
270
369
  }
271
- function computeSaltNonce(ownerAddress) {
370
+ function computeSaltNonce2(ownerAddress) {
272
371
  return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
273
372
  }
274
- function deriveSafeAddress(signerAddress) {
275
- return deriveSafeAddress$1(signerAddress, defaultSafeDeriveConfig);
373
+ function deriveSafeAddress2(signerAddress) {
374
+ return deriveSafeAddress(signerAddress, defaultSafeDeriveConfig);
276
375
  }
277
376
 
278
377
  // ../platform-kernel/src/ids.ts
@@ -928,7 +1027,7 @@ function createAccountsClient(config = {}) {
928
1027
  username: input.username,
929
1028
  countryCode: input.countryCode,
930
1029
  eoaAddress: input.signerProvider.signerAddress,
931
- safeAddress: deriveSafeAddress(input.signerProvider.signerAddress)
1030
+ safeAddress: deriveSafeAddress2(input.signerProvider.signerAddress)
932
1031
  }
933
1032
  );
934
1033
  const account = await config._data.query(
@@ -1514,8 +1613,17 @@ function createAuthClient(config = {}) {
1514
1613
  ];
1515
1614
  }
1516
1615
  try {
1517
- const safeAddress = deriveSafeAddress(signerAddress);
1518
- const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1616
+ const safeAddress = deriveSafeAddress2(signerAddress);
1617
+ if (!data.action) {
1618
+ return [
1619
+ new CapxulError({
1620
+ code: "INVALID_INPUT",
1621
+ message: "completeBootstrap requires a data client that can execute Convex actions."
1622
+ }),
1623
+ null
1624
+ ];
1625
+ }
1626
+ const result = await data.action(api.authBootstrap.completeBootstrap, {
1519
1627
  bootstrapToken: input.bootstrapToken,
1520
1628
  sessionToken: session.token,
1521
1629
  username: input.username,
@@ -1698,16 +1806,227 @@ function mutableConfig(config) {
1698
1806
  return config;
1699
1807
  }
1700
1808
 
1809
+ // src/core/auth-service.ts
1810
+ var AuthService = class {
1811
+ authClient;
1812
+ sessionStore;
1813
+ config;
1814
+ constructor(config = {}) {
1815
+ this.config = config;
1816
+ this.sessionStore = config.auth?.sessionStore ?? createMemorySessionStore2();
1817
+ this.authClient = createAuthClient({
1818
+ ...config,
1819
+ auth: { ...config.auth, sessionStore: this.sessionStore }
1820
+ });
1821
+ }
1822
+ async sendOtp(email, options) {
1823
+ const [err] = await this.authClient.sendOtp({ email }, options);
1824
+ if (err) throw err;
1825
+ }
1826
+ async verifyOtp(email, otp, options) {
1827
+ const [err, result] = await this.authClient.verifyOtp(
1828
+ { email, otp },
1829
+ options
1830
+ );
1831
+ if (err) throw err;
1832
+ return result;
1833
+ }
1834
+ async completeBootstrap(params, signer) {
1835
+ if (signer) {
1836
+ const tempClient = createAuthClient({
1837
+ ...this.config,
1838
+ signer,
1839
+ auth: { ...this.config.auth, sessionStore: this.sessionStore }
1840
+ });
1841
+ const [err2, result2] = await tempClient.completeBootstrap(params);
1842
+ if (err2) throw err2;
1843
+ return result2;
1844
+ }
1845
+ const [err, result] = await this.authClient.completeBootstrap(params);
1846
+ if (err) throw err;
1847
+ return result;
1848
+ }
1849
+ /**
1850
+ * Clears the persisted session and, when a transport was pre-injected,
1851
+ * drops the cached auth header.
1852
+ *
1853
+ * **Transport safety note:** `clearAuth()` is only invoked when
1854
+ * `config._transport` was supplied at construction (e.g. by the React
1855
+ * provider). If `AuthService` is instantiated directly in a Node/CLI
1856
+ * context without an injected transport, the transport-side auth cache
1857
+ * is the caller's responsibility.
1858
+ */
1859
+ async signOut() {
1860
+ if (!this.config._transport) {
1861
+ const [err] = await this.authClient.signOut();
1862
+ if (err) throw err;
1863
+ mutableConfig2(this.config)._data = void 0;
1864
+ return;
1865
+ }
1866
+ this.sessionStore.clear();
1867
+ this.config._transport.clearAuth();
1868
+ }
1869
+ async getSession() {
1870
+ const [err, session] = await this.authClient.getSession();
1871
+ if (err) throw err;
1872
+ return session;
1873
+ }
1874
+ };
1875
+ function mutableConfig2(config) {
1876
+ return config;
1877
+ }
1878
+ function createMemorySessionStore2() {
1879
+ let current = null;
1880
+ return {
1881
+ get: () => current,
1882
+ set: (session) => {
1883
+ current = session;
1884
+ },
1885
+ clear: () => {
1886
+ current = null;
1887
+ }
1888
+ };
1889
+ }
1890
+
1701
1891
  // src/core/documents.ts
1702
- function createDocumentsClient() {
1892
+ function requireInvoiceHash(row) {
1893
+ if (!row.invoiceHash) {
1894
+ throw new CapxulError({
1895
+ code: "INVALID_INPUT",
1896
+ message: `invoice document ${row.documentId ?? row._id} is missing its canonical invoiceHash`
1897
+ });
1898
+ }
1899
+ return row.invoiceHash;
1900
+ }
1901
+ function mapInvoiceRow(row) {
1902
+ const status = row.status === "cancelled" ? "canceled" : row.status === "pending" ? "open" : row.status === "overdue" ? "expired" : row.status;
1703
1903
  return {
1704
- create: async () => stub("documents.create"),
1705
- retrieve: async () => stub("documents.retrieve"),
1706
- list: async () => stub("documents.list"),
1707
- cancel: async () => stub("documents.cancel")
1904
+ object: "document",
1905
+ id: row.documentId ?? row._id,
1906
+ type: "invoice",
1907
+ owner: {
1908
+ kind: row.scope === "org" ? "organization" : "account",
1909
+ id: row.orgId ?? row.payeeEmail ?? row.payeeLabel
1910
+ },
1911
+ recipient: { email: row.payerEmail },
1912
+ amount: {
1913
+ value: row.amount,
1914
+ currency: row.currency
1915
+ },
1916
+ reference: row.note,
1917
+ lineItems: row.items,
1918
+ invoiceHash: requireInvoiceHash(row),
1919
+ dueAt: row.dueDate,
1920
+ status,
1921
+ createdAt: new Date(row.createdAt).toISOString()
1708
1922
  };
1709
1923
  }
1710
- function createOrgDocumentsClient() {
1924
+ function mapDocumentError(cause) {
1925
+ return fromConvexError(cause);
1926
+ }
1927
+ function createDocumentsClient(config = {}) {
1928
+ return {
1929
+ create: async (input) => {
1930
+ if (!config._data) return stub("documents.create");
1931
+ if (input.type !== "invoice") {
1932
+ return [
1933
+ new CapxulError({
1934
+ code: "INVALID_INPUT",
1935
+ message: "documents.create currently supports personal invoice documents only."
1936
+ }),
1937
+ null
1938
+ ];
1939
+ }
1940
+ if (!("email" in input.recipient)) {
1941
+ return [
1942
+ new CapxulError({
1943
+ code: "INVALID_INPUT",
1944
+ message: "personal invoice documents currently require an email recipient."
1945
+ }),
1946
+ null
1947
+ ];
1948
+ }
1949
+ try {
1950
+ const created = await config._data.mutation(
1951
+ api.paymentRecords.mutations.createInvoice,
1952
+ {
1953
+ scope: "personal",
1954
+ payerEmail: input.recipient.email,
1955
+ amount: input.amount.value,
1956
+ currency: input.amount.currency,
1957
+ dueDate: input.dueAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1958
+ note: input.reference,
1959
+ items: input.lineItems
1960
+ }
1961
+ );
1962
+ const row = await config._data.query(
1963
+ api.paymentRecords.queries.getInvoiceByDocumentId,
1964
+ { documentId: created.documentId }
1965
+ );
1966
+ if (!row) throw new Error("created invoice was not readable");
1967
+ return [null, mapInvoiceRow(row)];
1968
+ } catch (cause) {
1969
+ return [mapDocumentError(cause), null];
1970
+ }
1971
+ },
1972
+ retrieve: async (documentId) => {
1973
+ if (!config._data)
1974
+ return stub("documents.retrieve");
1975
+ try {
1976
+ const row = await config._data.query(
1977
+ api.paymentRecords.queries.getInvoiceByDocumentId,
1978
+ { documentId }
1979
+ );
1980
+ if (!row) {
1981
+ return [
1982
+ new CapxulError({
1983
+ code: "NOT_FOUND",
1984
+ message: `document ${documentId} not found`
1985
+ }),
1986
+ null
1987
+ ];
1988
+ }
1989
+ return [null, mapInvoiceRow(row)];
1990
+ } catch (cause) {
1991
+ return [mapDocumentError(cause), null];
1992
+ }
1993
+ },
1994
+ list: async (input = {}) => {
1995
+ if (!config._data)
1996
+ return stub("documents.list");
1997
+ try {
1998
+ if (input.type && input.type !== "invoice") {
1999
+ return [null, { object: "list", data: [], page: { hasMore: false } }];
2000
+ }
2001
+ const rows = await config._data.query(
2002
+ api.paymentRecords.queries.listMyInvoices,
2003
+ {
2004
+ limit: input.limit,
2005
+ cursor: input.cursor
2006
+ }
2007
+ );
2008
+ const page = rows;
2009
+ const data = page.data.map((row) => mapInvoiceRow(row));
2010
+ return [null, { object: "list", data, page: page.page }];
2011
+ } catch (cause) {
2012
+ return [mapDocumentError(cause), null];
2013
+ }
2014
+ },
2015
+ cancel: async (documentId) => {
2016
+ if (!config._data) return stub("documents.cancel");
2017
+ try {
2018
+ const row = await config._data.mutation(
2019
+ api.paymentRecords.mutations.cancelInvoice,
2020
+ { documentId }
2021
+ );
2022
+ return [null, mapInvoiceRow(row)];
2023
+ } catch (cause) {
2024
+ return [mapDocumentError(cause), null];
2025
+ }
2026
+ }
2027
+ };
2028
+ }
2029
+ function createOrgDocumentsClient(_config = {}) {
1711
2030
  return {
1712
2031
  create: async () => stub("organizations.documents.create"),
1713
2032
  retrieve: async () => stub("organizations.documents.retrieve"),
@@ -3035,7 +3354,9 @@ function createOrganizationsClient(config = {}) {
3035
3354
  },
3036
3355
  invite: async (input) => {
3037
3356
  if (!config._data?.action) {
3038
- return stub("organizations.members.invite");
3357
+ return stub(
3358
+ "organizations.members.invite"
3359
+ );
3039
3360
  }
3040
3361
  try {
3041
3362
  const orgId = input.organizationId.replace(/^org_/, "");
@@ -3123,7 +3444,9 @@ function createOrganizationsClient(config = {}) {
3123
3444
  },
3124
3445
  resend: async (input) => {
3125
3446
  if (!config._data?.action) {
3126
- return stub("organizations.members.resend");
3447
+ return stub(
3448
+ "organizations.members.resend"
3449
+ );
3127
3450
  }
3128
3451
  try {
3129
3452
  const orgId = input.organizationId.replace(/^org_/, "");
@@ -3191,7 +3514,7 @@ function createOrganizationsClient(config = {}) {
3191
3514
  payments: createOrgPaymentsClient(),
3192
3515
  transfers: createOrgTransfersClient(),
3193
3516
  withdrawals: createOrgWithdrawalsClient(config),
3194
- documents: createOrgDocumentsClient(),
3517
+ documents: createOrgDocumentsClient(config),
3195
3518
  webhookEndpoints: createWebhookEndpointsClient(),
3196
3519
  webhookEvents: createWebhookEventsClient()
3197
3520
  };
@@ -3308,609 +3631,6 @@ function createVirtualCardsClient() {
3308
3631
  cancel: async () => stub("virtualCards.cancel")
3309
3632
  };
3310
3633
  }
3311
- function createAuthFlowMachine(client) {
3312
- return setup({
3313
- types: {},
3314
- actors: {
3315
- // XState v5's `fromPromise` injects an `AbortSignal` that aborts
3316
- // when the actor is stopped (parent transition fires, machine is
3317
- // disposed, etc.). Plumbing it through `client.auth.sendOtp` /
3318
- // `client.auth.verifyOtp` makes the in-flight HTTP request
3319
- // cancellable: stale responses can't race a state machine
3320
- // that's already moved on. See PR #406 S5.
3321
- sendOtp: fromPromise(async ({ input, signal }) => {
3322
- const [error] = await client.auth.sendOtp(
3323
- { email: input.email },
3324
- { signal }
3325
- );
3326
- if (error) throw error;
3327
- }),
3328
- verifyOtp: fromPromise(
3329
- async ({ input, signal }) => {
3330
- const [error, result] = await client.auth.verifyOtp(
3331
- {
3332
- email: input.email,
3333
- otp: input.code
3334
- },
3335
- { signal }
3336
- );
3337
- if (error) throw error;
3338
- if (result.kind === "bootstrap_required") {
3339
- throw new CapxulError({
3340
- code: "ACTION_REQUIRED",
3341
- message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
3342
- details: { reason: result.reason }
3343
- });
3344
- }
3345
- return result.session;
3346
- }
3347
- ),
3348
- signOut: fromPromise(async () => {
3349
- const [error] = await client.auth.signOut();
3350
- if (error) throw error;
3351
- })
3352
- },
3353
- actions: {
3354
- trackOtpRequested: ({ context }) => {
3355
- if (!context.email) return;
3356
- track("auth_otp_requested", {
3357
- email_domain: emailDomain(context.email)
3358
- });
3359
- },
3360
- trackOtpFailed: ({ event }) => {
3361
- const error = errorFromEvent(event);
3362
- track("auth_failed", {
3363
- auth_type: "email_otp",
3364
- reason: error.code
3365
- });
3366
- },
3367
- trackTimeoutFailed: () => {
3368
- track("auth_failed", {
3369
- auth_type: "email_otp",
3370
- reason: "timeout"
3371
- });
3372
- },
3373
- trackVerified: () => {
3374
- track("auth_verified", { auth_type: "email_otp" });
3375
- },
3376
- identifyAndTrack: ({ context }) => {
3377
- if (!context.session) return;
3378
- identify(context.session.authUserId, {
3379
- email_domain: emailDomain(context.session.email)
3380
- });
3381
- track("auth_identified", {
3382
- email_domain: emailDomain(context.session.email)
3383
- });
3384
- },
3385
- trackSignedOut: () => {
3386
- track("auth_signed_out");
3387
- }
3388
- }
3389
- }).createMachine({
3390
- id: "auth",
3391
- initial: "idle",
3392
- context: { email: null, session: null, error: null },
3393
- states: {
3394
- idle: {
3395
- on: {
3396
- REQUEST_OTP: {
3397
- target: "sending_otp",
3398
- actions: assign({
3399
- email: ({ event }) => event.email,
3400
- error: () => null
3401
- })
3402
- }
3403
- }
3404
- },
3405
- sending_otp: {
3406
- invoke: {
3407
- src: "sendOtp",
3408
- input: ({ context }) => ({ email: requireEmail(context) }),
3409
- onDone: {
3410
- target: "otp_requested",
3411
- actions: ["trackOtpRequested"]
3412
- },
3413
- onError: {
3414
- target: "error",
3415
- actions: [
3416
- assign({ error: ({ event }) => errorFromEvent(event) }),
3417
- "trackOtpFailed"
3418
- ]
3419
- }
3420
- },
3421
- after: {
3422
- [FLOW_INVOKE_TIMEOUT_MS]: {
3423
- target: "error",
3424
- actions: [
3425
- assign({
3426
- error: () => timeoutError("sending_otp")
3427
- }),
3428
- "trackTimeoutFailed"
3429
- ]
3430
- }
3431
- }
3432
- },
3433
- otp_requested: {
3434
- on: {
3435
- VERIFY: { target: "verifying" },
3436
- RESET: {
3437
- target: "idle",
3438
- actions: assign({ email: () => null, error: () => null })
3439
- }
3440
- }
3441
- },
3442
- verifying: {
3443
- invoke: {
3444
- src: "verifyOtp",
3445
- input: ({ context, event }) => ({
3446
- email: requireEmail(context),
3447
- code: requireCodeFromEvent(event)
3448
- }),
3449
- onDone: {
3450
- target: "authenticated",
3451
- actions: [
3452
- // Scrub the duplicate `context.email` (input value
3453
- // captured during sendOtp) since the verified
3454
- // `session.email` is now the canonical source
3455
- // post-authentication. The session's email is
3456
- // intentionally retained — it's the auth result, not
3457
- // lingering input. See PR #406 S2.
3458
- assign({
3459
- session: ({ event }) => event.output,
3460
- email: () => null
3461
- }),
3462
- "trackVerified",
3463
- "identifyAndTrack"
3464
- ]
3465
- },
3466
- onError: {
3467
- target: "error",
3468
- actions: [
3469
- assign({ error: ({ event }) => errorFromEvent(event) }),
3470
- "trackOtpFailed"
3471
- ]
3472
- }
3473
- },
3474
- after: {
3475
- [FLOW_INVOKE_TIMEOUT_MS]: {
3476
- target: "error",
3477
- actions: [
3478
- assign({
3479
- error: () => timeoutError("verifying")
3480
- }),
3481
- "trackTimeoutFailed"
3482
- ]
3483
- }
3484
- }
3485
- },
3486
- authenticated: {
3487
- on: {
3488
- SIGN_OUT: { target: "signing_out" }
3489
- }
3490
- },
3491
- signing_out: {
3492
- invoke: {
3493
- src: "signOut",
3494
- onDone: {
3495
- target: "idle",
3496
- actions: [
3497
- assign({
3498
- session: () => null,
3499
- email: () => null,
3500
- error: () => null
3501
- }),
3502
- "trackSignedOut"
3503
- ]
3504
- },
3505
- onError: {
3506
- target: "error",
3507
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
3508
- }
3509
- }
3510
- },
3511
- error: {
3512
- on: {
3513
- RESET: {
3514
- target: "idle",
3515
- actions: assign({ error: () => null })
3516
- }
3517
- }
3518
- }
3519
- }
3520
- });
3521
- }
3522
- function requireEmail(context) {
3523
- if (!context.email) {
3524
- throw Errors.invalidInput(
3525
- "email",
3526
- "Auth flow advanced without an email captured in context."
3527
- );
3528
- }
3529
- return context.email;
3530
- }
3531
- function requireCodeFromEvent(event) {
3532
- if (event.type !== "VERIFY") {
3533
- throw Errors.invalidInput(
3534
- "code",
3535
- `Auth flow's verifying state requires a VERIFY event, got ${event.type}.`
3536
- );
3537
- }
3538
- return event.code;
3539
- }
3540
- function errorFromEvent(event) {
3541
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3542
- if (cause instanceof CapxulError) {
3543
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3544
- return new CapxulError({
3545
- code: cause.code,
3546
- message: redactEmail(cause.message),
3547
- cause,
3548
- details: cause.details,
3549
- operationId: cause.operationId,
3550
- correlationId: cause.correlationId,
3551
- retryable: cause.retryable
3552
- });
3553
- }
3554
- if (cause instanceof CapxulError2) {
3555
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3556
- return new CapxulError2(cause.code, redactEmail(cause.message), {
3557
- cause,
3558
- details: cause.details,
3559
- correlationId: cause.correlationId,
3560
- layer: cause.layer
3561
- });
3562
- }
3563
- return Errors.providerError("auth", "flow", redactCauseEmail(cause));
3564
- }
3565
- var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
3566
- var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
3567
- function redactEmail(message) {
3568
- return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
3569
- }
3570
- function redactCauseEmail(cause) {
3571
- if (cause instanceof Error) {
3572
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3573
- const redacted = new Error(redactEmail(cause.message));
3574
- redacted.cause = cause;
3575
- return redacted;
3576
- }
3577
- if (typeof cause === "string") {
3578
- return redactEmail(cause);
3579
- }
3580
- return cause;
3581
- }
3582
- function timeoutError(state) {
3583
- return Errors.providerError(
3584
- "auth",
3585
- "flow",
3586
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3587
- );
3588
- }
3589
- function emailDomain(email) {
3590
- const domain = email.split("@")[1]?.trim().toLowerCase();
3591
- return domain || "unknown";
3592
- }
3593
- var initialContext = {
3594
- email: null,
3595
- code: null,
3596
- username: null,
3597
- bootstrapToken: null,
3598
- bootstrapReason: null,
3599
- session: null,
3600
- account: null,
3601
- safe: null,
3602
- error: null
3603
- };
3604
- function createAuthBootstrapFlowMachine(client) {
3605
- return setup({
3606
- types: {},
3607
- actors: {
3608
- sendOtp: fromPromise(async ({ input, signal }) => {
3609
- const [error] = await client.auth.sendOtp(
3610
- { email: input.email },
3611
- { signal }
3612
- );
3613
- if (error) throw error;
3614
- }),
3615
- verifyOtp: fromPromise(
3616
- async ({ input, signal }) => {
3617
- const [error, result] = await client.auth.verifyOtp(
3618
- { email: input.email, otp: input.code },
3619
- { signal }
3620
- );
3621
- if (error) throw error;
3622
- return result;
3623
- }
3624
- ),
3625
- completeBootstrap: fromPromise(async ({ input }) => {
3626
- const [error, result] = await client.auth.completeBootstrap(input);
3627
- if (error) throw error;
3628
- return result;
3629
- }),
3630
- signOut: fromPromise(async () => {
3631
- const [error] = await client.auth.signOut();
3632
- if (error) throw error;
3633
- })
3634
- },
3635
- actions: {
3636
- trackOtpRequested: ({ context }) => {
3637
- if (!context.email) return;
3638
- track("auth_otp_requested", {
3639
- email_domain: emailDomain2(context.email)
3640
- });
3641
- },
3642
- trackFailed: ({ event }) => {
3643
- track("auth_failed", {
3644
- auth_type: "email_otp",
3645
- reason: errorFromEvent2(event).code
3646
- });
3647
- },
3648
- trackTimeoutFailed: () => {
3649
- track("auth_failed", {
3650
- auth_type: "email_otp",
3651
- reason: "timeout"
3652
- });
3653
- },
3654
- trackVerified: () => {
3655
- track("auth_verified", { auth_type: "email_otp" });
3656
- },
3657
- trackBootstrapRequired: ({ context }) => {
3658
- track("auth_verified", {
3659
- auth_type: "email_otp",
3660
- auth_mode: context.bootstrapReason ?? "bootstrap_required"
3661
- });
3662
- },
3663
- identifyAndTrack: ({ context }) => {
3664
- if (!context.session) return;
3665
- identify(context.session.authUserId, {
3666
- email_domain: emailDomain2(context.session.email)
3667
- });
3668
- track("auth_identified", {
3669
- email_domain: emailDomain2(context.session.email)
3670
- });
3671
- },
3672
- trackSignedOut: () => {
3673
- track("auth_signed_out");
3674
- }
3675
- }
3676
- }).createMachine({
3677
- id: "authBootstrap",
3678
- initial: "email",
3679
- context: initialContext,
3680
- states: {
3681
- email: {
3682
- on: {
3683
- ENTER_EMAIL: {
3684
- actions: assign({
3685
- email: ({ event }) => event.email,
3686
- error: () => null
3687
- })
3688
- },
3689
- REQUEST_OTP: { target: "sending_otp" }
3690
- }
3691
- },
3692
- sending_otp: {
3693
- invoke: {
3694
- src: "sendOtp",
3695
- input: ({ context }) => ({ email: requireEmail2(context) }),
3696
- onDone: { target: "otp_requested", actions: "trackOtpRequested" },
3697
- onError: {
3698
- target: "otp_requested",
3699
- actions: [
3700
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3701
- "trackFailed"
3702
- ]
3703
- }
3704
- },
3705
- after: {
3706
- [FLOW_INVOKE_TIMEOUT_MS]: {
3707
- target: "otp_requested",
3708
- actions: [
3709
- assign({ error: () => timeoutError2("sending_otp") }),
3710
- "trackTimeoutFailed"
3711
- ]
3712
- }
3713
- }
3714
- },
3715
- otp_requested: {
3716
- on: {
3717
- ENTER_OTP: {
3718
- actions: assign({
3719
- code: ({ event }) => event.code,
3720
- error: () => null
3721
- })
3722
- },
3723
- VERIFY_OTP: { target: "verifying_otp" },
3724
- BACK: { target: "email" },
3725
- RESET: { target: "email", actions: assign(() => initialContext) }
3726
- }
3727
- },
3728
- verifying_otp: {
3729
- invoke: {
3730
- src: "verifyOtp",
3731
- input: ({ context }) => ({
3732
- email: requireEmail2(context),
3733
- code: requireCode(context)
3734
- }),
3735
- onDone: [
3736
- {
3737
- guard: ({ event }) => event.output.kind === "existing_member",
3738
- target: "authenticated",
3739
- actions: [
3740
- assign({
3741
- session: ({ event }) => event.output.session,
3742
- account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
3743
- username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
3744
- safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
3745
- email: () => null,
3746
- error: () => null
3747
- }),
3748
- "trackVerified",
3749
- "identifyAndTrack"
3750
- ]
3751
- },
3752
- {
3753
- target: "bootstrap_required",
3754
- actions: [
3755
- assign({
3756
- session: ({ event }) => event.output.session,
3757
- bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
3758
- bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
3759
- username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
3760
- email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
3761
- error: () => null
3762
- }),
3763
- "trackVerified",
3764
- "trackBootstrapRequired"
3765
- ]
3766
- }
3767
- ],
3768
- onError: {
3769
- target: "otp_requested",
3770
- actions: [
3771
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3772
- "trackFailed"
3773
- ]
3774
- }
3775
- },
3776
- after: {
3777
- [FLOW_INVOKE_TIMEOUT_MS]: {
3778
- target: "otp_requested",
3779
- actions: [
3780
- assign({ error: () => timeoutError2("verifying_otp") }),
3781
- "trackTimeoutFailed"
3782
- ]
3783
- }
3784
- }
3785
- },
3786
- bootstrap_required: {
3787
- on: {
3788
- ENTER_USERNAME: {
3789
- actions: assign({
3790
- username: ({ event }) => event.username,
3791
- error: () => null
3792
- })
3793
- },
3794
- COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3795
- BACK: { target: "otp_requested" },
3796
- RESET: { target: "email", actions: assign(() => initialContext) }
3797
- }
3798
- },
3799
- completing_bootstrap: {
3800
- invoke: {
3801
- src: "completeBootstrap",
3802
- input: ({ context }) => ({
3803
- bootstrapToken: requireBootstrapToken(context),
3804
- username: requireUsername(context)
3805
- }),
3806
- onDone: {
3807
- target: "authenticated",
3808
- actions: [
3809
- assign({
3810
- session: ({ event }) => event.output.session,
3811
- account: ({ event }) => event.output.account,
3812
- username: ({ event }) => event.output.username,
3813
- safe: ({ event }) => event.output.safe,
3814
- bootstrapToken: () => null,
3815
- bootstrapReason: () => null,
3816
- email: () => null,
3817
- error: () => null
3818
- }),
3819
- "identifyAndTrack"
3820
- ]
3821
- },
3822
- onError: {
3823
- target: "bootstrap_required",
3824
- actions: [
3825
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3826
- "trackFailed"
3827
- ]
3828
- }
3829
- },
3830
- after: {
3831
- [FLOW_INVOKE_TIMEOUT_MS]: {
3832
- target: "bootstrap_required",
3833
- actions: [
3834
- assign({ error: () => timeoutError2("completing_bootstrap") }),
3835
- "trackTimeoutFailed"
3836
- ]
3837
- }
3838
- }
3839
- },
3840
- authenticated: {
3841
- on: {
3842
- SIGN_OUT: { target: "signing_out" }
3843
- }
3844
- },
3845
- signing_out: {
3846
- invoke: {
3847
- src: "signOut",
3848
- onDone: {
3849
- target: "email",
3850
- actions: [
3851
- assign(() => initialContext),
3852
- "trackSignedOut"
3853
- ]
3854
- },
3855
- onError: {
3856
- target: "error",
3857
- actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3858
- }
3859
- }
3860
- },
3861
- error: {
3862
- on: {
3863
- RESET: { target: "email", actions: assign(() => initialContext) }
3864
- }
3865
- }
3866
- }
3867
- });
3868
- }
3869
- function requireEmail2(context) {
3870
- if (!context.email) {
3871
- throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3872
- }
3873
- return context.email;
3874
- }
3875
- function requireCode(context) {
3876
- if (!context.code) {
3877
- throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3878
- }
3879
- return context.code;
3880
- }
3881
- function requireBootstrapToken(context) {
3882
- if (!context.bootstrapToken) {
3883
- throw Errors.invalidInput(
3884
- "bootstrapToken",
3885
- "Auth bootstrap requires a continuation token."
3886
- );
3887
- }
3888
- return context.bootstrapToken;
3889
- }
3890
- function requireUsername(context) {
3891
- if (!context.username) {
3892
- throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3893
- }
3894
- return context.username;
3895
- }
3896
- function errorFromEvent2(event) {
3897
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3898
- if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3899
- return cause;
3900
- }
3901
- return Errors.providerError("auth", "bootstrap", cause);
3902
- }
3903
- function timeoutError2(state) {
3904
- return Errors.providerError(
3905
- "auth",
3906
- "bootstrap",
3907
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3908
- );
3909
- }
3910
- function emailDomain2(email) {
3911
- const domain = email.split("@")[1]?.trim().toLowerCase();
3912
- return domain || "unknown";
3913
- }
3914
3634
  function createProvisioningMachine(client) {
3915
3635
  return setup({
3916
3636
  types: {},
@@ -3938,7 +3658,7 @@ function createProvisioningMachine(client) {
3938
3658
  const provider = context.input?.signerProvider;
3939
3659
  if (!provider) return;
3940
3660
  track("provisioning_safe_created", {
3941
- safe_address: deriveSafeAddress(provider.signerAddress)
3661
+ safe_address: deriveSafeAddress2(provider.signerAddress)
3942
3662
  });
3943
3663
  }
3944
3664
  }
@@ -3983,13 +3703,13 @@ function createProvisioningMachine(client) {
3983
3703
  },
3984
3704
  onError: {
3985
3705
  target: "error",
3986
- actions: assign({ error: ({ event }) => errorFromEvent3(event) })
3706
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
3987
3707
  }
3988
3708
  },
3989
3709
  after: {
3990
3710
  [FLOW_INVOKE_TIMEOUT_MS]: {
3991
3711
  target: "error",
3992
- actions: assign({ error: () => timeoutError3() })
3712
+ actions: assign({ error: () => timeoutError() })
3993
3713
  }
3994
3714
  }
3995
3715
  },
@@ -4007,7 +3727,7 @@ function createProvisioningMachine(client) {
4007
3727
  * this payload on its `onDone` transition and branches via guards
4008
3728
  * on `event.output.error`.
4009
3729
  */
4010
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
3730
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError() }
4011
3731
  });
4012
3732
  }
4013
3733
  function requireProvisionInput(context) {
@@ -4019,13 +3739,13 @@ function requireProvisionInput(context) {
4019
3739
  }
4020
3740
  return context.input;
4021
3741
  }
4022
- function errorFromEvent3(event) {
3742
+ function errorFromEvent(event) {
4023
3743
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4024
3744
  if (cause instanceof CapxulError) return cause;
4025
3745
  if (cause instanceof CapxulError2) return cause;
4026
3746
  return Errors.providerError("provisioning", "flow", cause);
4027
3747
  }
4028
- function timeoutError3() {
3748
+ function timeoutError() {
4029
3749
  return Errors.providerError(
4030
3750
  "provisioning",
4031
3751
  "flow",
@@ -4118,7 +3838,7 @@ function createOnboardingFlowMachine(client) {
4118
3838
  error: ({ event }) => extractChildErrorOrFallback(event)
4119
3839
  }),
4120
3840
  assignChildThrown: assign({
4121
- error: ({ event }) => errorFromEvent4(event)
3841
+ error: ({ event }) => errorFromEvent2(event)
4122
3842
  }),
4123
3843
  assignAccountFromChild: assign({
4124
3844
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -4280,7 +4000,7 @@ function extractChildAccountOrNull(event) {
4280
4000
  if (output && "account" in output && output.account) return output.account;
4281
4001
  return null;
4282
4002
  }
4283
- function errorFromEvent4(event) {
4003
+ function errorFromEvent2(event) {
4284
4004
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4285
4005
  if (cause instanceof CapxulError) return cause;
4286
4006
  if (cause instanceof CapxulError2) return cause;
@@ -4291,7 +4011,7 @@ function errorFromEvent4(event) {
4291
4011
  function createCapxulClient(config = {}) {
4292
4012
  const clientWithoutFlows = {
4293
4013
  id: crypto.randomUUID(),
4294
- auth: createAuthClient(config),
4014
+ auth: new AuthService(config),
4295
4015
  me: createMeClient(config),
4296
4016
  accounts: createAccountsClient(config),
4297
4017
  organizations: createOrganizationsClient(config),
@@ -4299,7 +4019,7 @@ function createCapxulClient(config = {}) {
4299
4019
  transfers: createTransfersClient(),
4300
4020
  tokenTransfers: createTokenTransfersClient(config),
4301
4021
  withdrawals: createWithdrawalsClient(config),
4302
- documents: createDocumentsClient(),
4022
+ documents: createDocumentsClient(config),
4303
4023
  subAccounts: createSubAccountsClient(config),
4304
4024
  virtualAccounts: createVirtualAccountsClient(),
4305
4025
  virtualCards: createVirtualCardsClient(),
@@ -4311,8 +4031,6 @@ function createCapxulClient(config = {}) {
4311
4031
  };
4312
4032
  const client = clientWithoutFlows;
4313
4033
  client.flows = {
4314
- auth: () => createAuthFlowMachine(client),
4315
- authBootstrap: () => createAuthBootstrapFlowMachine(client),
4316
4034
  onboarding: () => createOnboardingFlowMachine(client),
4317
4035
  provisioning: () => createProvisioningMachine(client)
4318
4036
  };