@capxul/sdk-react 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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @capxul/sdk-react
2
2
 
3
+ ## 0.2.0-alpha.4
4
+
5
+ ### Minor Changes
6
+
7
+ - b6c1f94: Complete the auth dogfooding epic around the canonical auth surface.
8
+
9
+ The SDK now carries the guarded OTP proof path, bootstrap continuation states,
10
+ canonical auth service behavior, and final funnel proof support needed for
11
+ first-run auth dogfooding. The React SDK aligns its auth hooks and provider
12
+ state with that canonical flow, including signout cleanup, bootstrap-required
13
+ continuations, and funnel telemetry integration.
14
+
15
+ ### Patch Changes
16
+
17
+ - 50d3c55: Bundle the internal Safe derivation package into the SDK artifact instead of
18
+ publishing it as a runtime dependency, and add a packaging guard that fails when
19
+ publishable packages leak private `@repo/*` runtime dependencies.
20
+ - Updated dependencies [b6c1f94]
21
+ - Updated dependencies [50d3c55]
22
+ - @capxul/sdk@0.2.0-alpha.4
23
+
3
24
  ## 0.2.0-alpha.3
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -68,29 +68,23 @@ function Header() {
68
68
  }
69
69
  ```
70
70
 
71
- Drive an OTP sign-in flow with `useAuthFlow()` — a
72
- [`useActor`](https://stately.ai/docs/xstate-react#useactor) observer
73
- over the canonical XState v5 machine in `@capxul/sdk`:
71
+ Drive an OTP sign-in flow with `useAuth()` — the canonical React auth
72
+ wrapper over `AuthService`:
74
73
 
75
74
  ```tsx
76
- import { useAuthFlow } from "@capxul/sdk-react";
77
- import { toEmail } from "@capxul/sdk";
75
+ import { useAuth } from "@capxul/sdk-react";
78
76
 
79
77
  function SignIn() {
80
- const { snapshot, send } = useAuthFlow();
78
+ const auth = useAuth();
81
79
 
82
- if (snapshot.matches("idle")) {
80
+ if (auth.state === "idle") {
83
81
  return (
84
- <button
85
- onClick={() =>
86
- send({ type: "REQUEST_OTP", email: toEmail("alice@example.com") })
87
- }
88
- >
82
+ <button onClick={() => auth.signIn("alice@example.com")}>
89
83
  Send code
90
84
  </button>
91
85
  );
92
86
  }
93
- // … handle "sending_otp", "otp_requested", "verifying", "authenticated"
87
+ // … handle "sendingOtp", "awaitingOtp", "bootstrapping", "authenticated"
94
88
  }
95
89
  ```
96
90
 
@@ -106,11 +100,11 @@ function Banner() {
106
100
  const status = useCapxulStatus();
107
101
 
108
102
  switch (status.status) {
109
- case "idle": // no network call yet
110
- case "bootstrapping": // /v1/client/bootstrap in flight
103
+ case "idle": // no network call yet
104
+ case "bootstrapping": // /v1/client/bootstrap in flight
111
105
  return <Spinner />;
112
- case "ready": // bootstrapped, no session
113
- case "authenticated": // bootstrapped + session live
106
+ case "ready": // bootstrapped, no session
107
+ case "authenticated": // bootstrapped + session live
114
108
  return null;
115
109
  case "error":
116
110
  return <ErrorBanner error={status.error} />;
@@ -129,11 +123,11 @@ itself never re-renders.
129
123
  The publishable-key path is runtime-proven where the repo can run it
130
124
  without secrets:
131
125
 
132
- | Surface | Runtime proof | Expected safe signal |
133
- |---|---|---|
134
- | Provider + `useMe()` | `corepack pnpm --filter @capxul/sdk-react check-types` plus the headless proof tests under `packages/sdk-react/ops/proof` | provider reaches `useCapxulStatus().status === "ready"` after one bootstrap request |
135
- | Reference CLI mock | `corepack pnpm --filter @capxul/reference-cli build && node apps/reference-cli/dist/cli.js bootstrap probe --mock --json` | `ok:true`, `mode:"publishable-key"`, `bootstrapRequests:1`, `authRequests:1`, `keyLengthClass:"provided"` |
136
- | Reference CLI live | same command without `--mock`, with `CAPXUL_REF_PUBLISHABLE_KEY` and optional `CAPXUL_REF_BOOTSTRAP_URL` set locally | success only when the key/origin/runtime are valid; otherwise sanitized SDK error JSON |
126
+ | Surface | Runtime proof | Expected safe signal |
127
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
128
+ | Provider + `useMe()` | `corepack pnpm --filter @capxul/sdk-react check-types` plus the headless proof tests under `packages/sdk-react/ops/proof` | provider reaches `useCapxulStatus().status === "ready"` after one bootstrap request |
129
+ | Reference CLI mock | `corepack pnpm --filter @capxul/reference-cli build && node apps/reference-cli/dist/cli.js bootstrap probe --mock --json` | `ok:true`, `mode:"publishable-key"`, `bootstrapRequests:1`, `authRequests:1`, `keyLengthClass:"provided"` |
130
+ | Reference CLI live | same command without `--mock`, with `CAPXUL_REF_PUBLISHABLE_KEY` and optional `CAPXUL_REF_BOOTSTRAP_URL` set locally | success only when the key/origin/runtime are valid; otherwise sanitized SDK error JSON |
137
131
 
138
132
  Do not paste or commit publishable keys, session tokens, Convex JWTs,
139
133
  cookies, or provider payloads. Proof output reports only
@@ -141,22 +135,22 @@ cookies, or provider payloads. Proof output reports only
141
135
 
142
136
  ## Hooks catalogue
143
137
 
144
- | Surface | Hook |
145
- |---|---|
146
- | Imperative client | `useCapxul()` |
147
- | Identity | `useMe`, `useAccount` |
148
- | Organizations | `useOrganization`, `useOrganizations`, `useMember`, `useMembers` |
149
- | Payments | `usePayment`, `usePayments`, `useOrgPayments` |
150
- | Transfers | `useTransfer`, `useTransfers`, `useOrgTransfers` |
151
- | Withdrawals | `useWithdrawal`, `useWithdrawals`, `useOrgWithdrawals` |
152
- | Documents | `useDocument`, `useDocuments`, `useOrgDocuments` |
153
- | Sub-accounts / virtual | `useSubAccount`, `useSubAccounts`, `useVirtualAccount`, `useVirtualAccounts`, `useVirtualCard`, `useVirtualCards` |
154
- | Settings | `useApiKey`, `useApiKeys`, `useWebhookEndpoint`, `useWebhookEndpoints`, `useWebhookEvent`, `useExternalAccount`, `useExternalAccounts`, `useBalanceLedgerEntry`, `useBalanceLedger` |
155
- | KYC / KYB | `useKycProfile` |
156
- | Treasury | `useTreasury`, `useSafe` |
157
- | Operations | `useOperation` (correlation join key per CANON.md §3.3) |
158
- | Lifecycle | `useCapxulStatus` |
159
- | Flows | `useAuthFlow`, `useOnboardingFlow`, `useProvisioningFlow` |
138
+ | Surface | Hook |
139
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | Imperative client | `useCapxul()` |
141
+ | Identity | `useMe`, `useAccount` |
142
+ | Organizations | `useOrganization`, `useOrganizations`, `useMember`, `useMembers` |
143
+ | Payments | `usePayment`, `usePayments`, `useOrgPayments` |
144
+ | Transfers | `useTransfer`, `useTransfers`, `useOrgTransfers` |
145
+ | Withdrawals | `useWithdrawal`, `useWithdrawals`, `useOrgWithdrawals` |
146
+ | Documents | `useDocument`, `useDocuments`, `useOrgDocuments` |
147
+ | Sub-accounts / virtual | `useSubAccount`, `useSubAccounts`, `useVirtualAccount`, `useVirtualAccounts`, `useVirtualCard`, `useVirtualCards` |
148
+ | Settings | `useApiKey`, `useApiKeys`, `useWebhookEndpoint`, `useWebhookEndpoints`, `useWebhookEvent`, `useExternalAccount`, `useExternalAccounts`, `useBalanceLedgerEntry`, `useBalanceLedger` |
149
+ | KYC / KYB | `useKycProfile` |
150
+ | Treasury | `useTreasury`, `useSafe` |
151
+ | Operations | `useOperation` (correlation join key per CANON.md §3.3) |
152
+ | Lifecycle | `useCapxulStatus` |
153
+ | Flows | `useOnboardingFlow`, `useProvisioningFlow` |
160
154
 
161
155
  Most "not-yet-implemented" verticals return a `QueryResult<T>` in the
162
156
  `error` state with `code: "NOT_IMPLEMENTED"` — they compile, render
@@ -169,7 +163,7 @@ Read hooks return a discriminated union:
169
163
  ```ts
170
164
  type QueryResult<T> =
171
165
  | { readonly status: "loading" }
172
- | { readonly status: "data"; readonly data: T }
166
+ | { readonly status: "data"; readonly data: T }
173
167
  | { readonly status: "error"; readonly error: CapxulError };
174
168
  ```
175
169
 
package/dist/index.cjs CHANGED
@@ -859,12 +859,62 @@ function useResendInvitation() {
859
859
  }
860
860
  });
861
861
  }
862
+
863
+ // ../observability/src/debug-log.ts
864
+ function isDevelopmentBuild() {
865
+ if (typeof process === "undefined") {
866
+ return false;
867
+ }
868
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
869
+ }
870
+ function debugLog(line) {
871
+ if (!isDevelopmentBuild()) return;
872
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
873
+ console.info(line);
874
+ return;
875
+ }
876
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
877
+ process.stderr.write(`${line}
878
+ `);
879
+ }
880
+ }
881
+ function formatDebugValue(value) {
882
+ if (value === void 0 || value === "") return "";
883
+ if (typeof value === "string") return value;
884
+ try {
885
+ return JSON.stringify(value);
886
+ } catch {
887
+ return String(value);
888
+ }
889
+ }
890
+ function track(...args) {
891
+ const [name, props] = args;
892
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
893
+ }
894
+ function formatDebugValue2(value) {
895
+ if (value === void 0 || value === "") return "";
896
+ if (typeof value === "string") return value;
897
+ try {
898
+ return JSON.stringify(value);
899
+ } catch {
900
+ return String(value);
901
+ }
902
+ }
903
+ function identify(userId, traits) {
904
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
905
+ }
906
+ function resetIdentity() {
907
+ }
862
908
  function useAuth(options) {
863
909
  const authService = useAuthService();
864
910
  const signerProvisioner = useMemoizedSignerProvisioner();
865
911
  const injectedSigner = options?.signer;
866
912
  const [state, setState] = react.useState("idle");
867
913
  const [user, setUser] = react.useState(null);
914
+ const [bootstrap, setBootstrap] = react.useState(
915
+ null
916
+ );
917
+ const bootstrapRef = react.useRef(null);
868
918
  const [error, setError] = react.useState(null);
869
919
  const signIn = react.useCallback(
870
920
  async (email) => {
@@ -872,9 +922,13 @@ function useAuth(options) {
872
922
  setError(null);
873
923
  try {
874
924
  await authService.sendOtp(email);
925
+ track("auth_otp_requested", {
926
+ email_domain: emailDomain(email)
927
+ });
875
928
  setState("awaitingOtp");
876
929
  } catch (err) {
877
930
  const wrapped = err instanceof Error ? err : new Error(String(err));
931
+ trackAuthFailure("otp_request", wrapped);
878
932
  setError(wrapped);
879
933
  setState("error");
880
934
  throw wrapped;
@@ -889,25 +943,96 @@ function useAuth(options) {
889
943
  try {
890
944
  const result = await authService.verifyOtp(email, otp);
891
945
  if (result.kind === "existing_member") {
892
- const nextUser2 = {
946
+ track("auth_otp_verified", {
947
+ email_domain: emailDomain(result.session.email),
948
+ branch: "existing_member"
949
+ });
950
+ const nextUser = {
893
951
  account: result.account,
894
952
  username: result.username,
895
953
  safe: result.safe,
896
954
  session: result.session
897
955
  };
898
- setUser(nextUser2);
956
+ setUser(nextUser);
957
+ setBootstrap(null);
958
+ bootstrapRef.current = null;
959
+ identify(result.session.authUserId, {
960
+ auth_branch: "existing_member"
961
+ });
962
+ track("auth_session_ready", {
963
+ branch: "existing_member",
964
+ has_convex_jwt: typeof result.session.convexJwt === "string"
965
+ });
899
966
  setState("authenticated");
900
- return result.session;
967
+ return {
968
+ kind: "existing_member",
969
+ session: result.session,
970
+ user: nextUser
971
+ };
901
972
  }
973
+ const nextBootstrap = {
974
+ bootstrapToken: result.bootstrapToken,
975
+ email: result.email,
976
+ reason: result.reason,
977
+ username: result.username,
978
+ session: result.session
979
+ };
980
+ setUser(null);
981
+ setBootstrap(nextBootstrap);
982
+ bootstrapRef.current = nextBootstrap;
983
+ track("auth_otp_verified", {
984
+ email_domain: emailDomain(result.session.email),
985
+ branch: "bootstrap_required"
986
+ });
987
+ track("auth_bootstrap_required", {
988
+ reason: result.reason,
989
+ has_suggested_username: result.username !== void 0,
990
+ has_convex_jwt: typeof result.session.convexJwt === "string"
991
+ });
992
+ setState("bootstrapRequired");
993
+ return {
994
+ kind: "bootstrap_required",
995
+ session: result.session,
996
+ bootstrap: nextBootstrap
997
+ };
998
+ } catch (err) {
999
+ const wrapped = err instanceof Error ? err : new Error(String(err));
1000
+ trackAuthFailure("otp_verify", wrapped);
1001
+ setError(wrapped);
1002
+ setState("error");
1003
+ throw wrapped;
1004
+ }
1005
+ },
1006
+ [authService]
1007
+ );
1008
+ const completeBootstrap = react.useCallback(
1009
+ async (username, signer) => {
1010
+ const pendingBootstrap = bootstrapRef.current;
1011
+ if (!pendingBootstrap) {
1012
+ const missing = new Error(
1013
+ "completeBootstrap requires a prior bootstrap_required OTP result."
1014
+ );
1015
+ track("auth_session_lost", { step: "complete_bootstrap" });
1016
+ setError(missing);
1017
+ setState("error");
1018
+ throw missing;
1019
+ }
1020
+ setError(null);
1021
+ track("auth_username_submitted", {
1022
+ has_suggested_username: pendingBootstrap.username !== void 0
1023
+ });
1024
+ setState("provisioningSigner");
1025
+ try {
1026
+ const signerKind = signer ? "provided" : injectedSigner ? "configured" : "generated";
1027
+ const selectedSigner = signer ?? injectedSigner ?? signerProvisioner.provision().signer;
1028
+ track("auth_signer_provisioned", { signer_kind: signerKind });
902
1029
  setState("bootstrapping");
903
- const signer = injectedSigner ?? signerProvisioner.provision().signer;
904
- const username = result.username ?? generateUsernameFromEmail(result.email);
905
1030
  const bootstrapResult = await authService.completeBootstrap(
906
1031
  {
907
- bootstrapToken: result.bootstrapToken,
1032
+ bootstrapToken: pendingBootstrap.bootstrapToken,
908
1033
  username
909
1034
  },
910
- signer
1035
+ selectedSigner
911
1036
  );
912
1037
  const nextUser = {
913
1038
  account: bootstrapResult.account,
@@ -915,26 +1040,47 @@ function useAuth(options) {
915
1040
  safe: bootstrapResult.safe,
916
1041
  session: bootstrapResult.session
917
1042
  };
1043
+ track("auth_safe_provisioned", {
1044
+ safe_status: bootstrapResult.safe.status
1045
+ });
918
1046
  setUser(nextUser);
1047
+ setBootstrap(null);
1048
+ bootstrapRef.current = null;
1049
+ identify(bootstrapResult.session.authUserId, {
1050
+ auth_branch: "bootstrap_required"
1051
+ });
1052
+ track("auth_bootstrap_completed", {
1053
+ reason: pendingBootstrap.reason
1054
+ });
1055
+ track("auth_session_ready", {
1056
+ branch: "bootstrap_required",
1057
+ has_convex_jwt: typeof bootstrapResult.session.convexJwt === "string"
1058
+ });
919
1059
  setState("authenticated");
920
- return bootstrapResult.session;
1060
+ return nextUser;
921
1061
  } catch (err) {
922
1062
  const wrapped = err instanceof Error ? err : new Error(String(err));
1063
+ trackAuthFailure("complete_bootstrap", wrapped);
923
1064
  setError(wrapped);
924
1065
  setState("error");
925
1066
  throw wrapped;
926
1067
  }
927
1068
  },
928
- [authService, signerProvisioner, injectedSigner]
1069
+ [authService, injectedSigner, signerProvisioner]
929
1070
  );
930
1071
  const signOut = react.useCallback(async () => {
931
1072
  setError(null);
932
1073
  try {
933
1074
  await authService.signOut();
1075
+ track("auth_signed_out");
1076
+ resetIdentity();
934
1077
  setUser(null);
1078
+ setBootstrap(null);
1079
+ bootstrapRef.current = null;
935
1080
  setState("idle");
936
1081
  } catch (err) {
937
1082
  const wrapped = err instanceof Error ? err : new Error(String(err));
1083
+ trackAuthFailure("sign_out", wrapped);
938
1084
  setError(wrapped);
939
1085
  setState("error");
940
1086
  throw wrapped;
@@ -944,48 +1090,46 @@ function useAuth(options) {
944
1090
  () => ({
945
1091
  state,
946
1092
  user,
1093
+ bootstrap,
947
1094
  error,
948
1095
  signIn,
949
1096
  verifyOtp,
1097
+ completeBootstrap,
950
1098
  signOut
951
1099
  }),
952
- [state, user, error, signIn, verifyOtp, signOut]
1100
+ [
1101
+ state,
1102
+ user,
1103
+ bootstrap,
1104
+ error,
1105
+ signIn,
1106
+ verifyOtp,
1107
+ completeBootstrap,
1108
+ signOut
1109
+ ]
953
1110
  );
954
1111
  }
955
1112
  function useMemoizedSignerProvisioner() {
956
1113
  const [provisioner] = react.useState(() => new sdk.SignerProvisioner());
957
1114
  return provisioner;
958
1115
  }
959
- function generateUsernameFromEmail(email) {
960
- const local = email.split("@")[0]?.toLowerCase() ?? "user";
961
- let sanitized = local.replace(/[^a-z0-9_-]/g, "_");
962
- if (/^[0-9]/.test(sanitized)) {
963
- sanitized = "u" + sanitized;
964
- }
965
- if (sanitized.length < 3) {
966
- sanitized = sanitized + "_".repeat(3 - sanitized.length);
967
- }
968
- if (sanitized.length > 30) {
969
- sanitized = sanitized.slice(0, 30);
970
- }
971
- try {
972
- return sdk.toUsername(sanitized);
973
- } catch {
974
- const fallback = `user_${Date.now() % 1e6}`;
975
- return sdk.toUsername(fallback);
976
- }
1116
+ function emailDomain(email) {
1117
+ const domain = email.split("@")[1]?.trim().toLowerCase();
1118
+ return domain && /^[a-z0-9.-]+$/.test(domain) ? domain : "unknown";
977
1119
  }
978
- function useAuthFlow() {
979
- const client = useCapxul();
980
- const machine = react.useMemo(() => client.flows.auth(), [client]);
981
- const [snapshot, send] = react$1.useActor(machine);
982
- return { snapshot, send };
1120
+ function trackAuthFailure(step, error) {
1121
+ const reason = errorCode(error);
1122
+ if (reason === "PERMISSION_DENIED" || reason === "AUTHZ_DENIED") {
1123
+ track("authz_denied", { step, reason });
1124
+ }
1125
+ track("auth_failed", {
1126
+ step,
1127
+ reason
1128
+ });
983
1129
  }
984
- function useAuthBootstrapFlow() {
985
- const client = useCapxul();
986
- const machine = react.useMemo(() => client.flows.authBootstrap(), [client]);
987
- const [snapshot, send] = react$1.useActor(machine);
988
- return { snapshot, send };
1130
+ function errorCode(error) {
1131
+ const code = error.code;
1132
+ return typeof code === "string" && code.length > 0 ? code : error.name || "Error";
989
1133
  }
990
1134
  function useOnboardingFlow() {
991
1135
  const client = useCapxul();
@@ -1097,8 +1241,6 @@ exports.useAccountBalanceLedger = useAccountBalanceLedger;
1097
1241
  exports.useApiKey = useApiKey;
1098
1242
  exports.useApiKeys = useApiKeys;
1099
1243
  exports.useAuth = useAuth;
1100
- exports.useAuthBootstrapFlow = useAuthBootstrapFlow;
1101
- exports.useAuthFlow = useAuthFlow;
1102
1244
  exports.useBalanceLedger = useBalanceLedger;
1103
1245
  exports.useBalanceLedgerEntry = useBalanceLedgerEntry;
1104
1246
  exports.useCapxul = useCapxul;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
- import * as _capxul_sdk from '@capxul/sdk';
3
- import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, MembershipStatus, TokenTransfersListInput, TokenTransfersListPage, MemberInviteResponse, Session } from '@capxul/sdk';
4
- export { AuthBootstrapFlowContext, AuthBootstrapFlowEvent, AuthFlowContext, AuthFlowEvent, BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
2
+ import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, MembershipStatus, TokenTransfersListInput, TokenTransfersListPage, MemberInviteResponse, Session, AuthBootstrapToken, AuthBootstrapReason } from '@capxul/sdk';
3
+ export { BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
5
4
  import { QueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
6
5
  import { CapxulClient } from '@capxul/sdk/client';
7
6
  import { CapxulError } from '@capxul/sdk/errors';
@@ -517,14 +516,6 @@ declare function useRevokeMember(): UseMutationResult<Member, CapxulError$1, Rev
517
516
  declare function useRemoveMember(): UseMutationResult<void, CapxulError$1, RemoveMemberArgs>;
518
517
  declare function useResendInvitation(): UseMutationResult<MemberInviteResponse, CapxulError$1, ResendInvitationArgs>;
519
518
 
520
- /**
521
- * Lowercased, shape-validated email address. Brand prevents swapping
522
- * with `phoneNumber`, `username`, or other string identifiers (per
523
- * `CANON.md` §4.54 and `sdk-surface.md` §5h).
524
- */
525
- type Email = string & {
526
- readonly __capxulEmailBrand: "Email";
527
- };
528
519
  /**
529
520
  * Public Capxul username. 3–30 chars, letter-first, lowercased,
530
521
  * remaining chars from `[a-z0-9_-]` (per `CANON.md` §4.54 and
@@ -543,13 +534,31 @@ type User = {
543
534
  readonly safe: Safe;
544
535
  readonly session: Session;
545
536
  };
546
- type AuthState = "idle" | "sendingOtp" | "awaitingOtp" | "bootstrapping" | "authenticated" | "error";
537
+ type AuthState = "idle" | "sendingOtp" | "awaitingOtp" | "bootstrapRequired" | "provisioningSigner" | "bootstrapping" | "authenticated" | "error";
538
+ type BootstrapContinuation = {
539
+ readonly bootstrapToken: AuthBootstrapToken;
540
+ readonly email: string;
541
+ readonly reason: AuthBootstrapReason;
542
+ readonly username?: Username;
543
+ readonly session: Session;
544
+ };
545
+ type AuthVerifyResult = {
546
+ readonly kind: "existing_member";
547
+ readonly session: Session;
548
+ readonly user: User;
549
+ } | {
550
+ readonly kind: "bootstrap_required";
551
+ readonly session: Session;
552
+ readonly bootstrap: BootstrapContinuation;
553
+ };
547
554
  type UseAuthResult = {
548
555
  readonly state: AuthState;
549
556
  readonly user: User | null;
557
+ readonly bootstrap: BootstrapContinuation | null;
550
558
  readonly error: Error | null;
551
559
  readonly signIn: (email: string) => Promise<void>;
552
- readonly verifyOtp: (email: string, otp: string) => Promise<Session>;
560
+ readonly verifyOtp: (email: string, otp: string) => Promise<AuthVerifyResult>;
561
+ readonly completeBootstrap: (username: Username, signer?: Account$1) => Promise<User>;
553
562
  readonly signOut: () => Promise<void>;
554
563
  };
555
564
  type UseAuthOptions = {
@@ -557,100 +566,24 @@ type UseAuthOptions = {
557
566
  readonly signer?: Account$1;
558
567
  };
559
568
  /**
560
- * Canonical auth hook — replaces `useAuthFlow` and `useAuthBootstrapFlow`.
569
+ * Canonical auth hook.
561
570
  *
562
571
  * Reactive state (`state`, `user`, `error`) is suitable for UI
563
572
  * observers; `signIn`, `verifyOtp`, and `signOut` return promises so
564
573
  * the reference CLI can drive the flow imperatively.
565
574
  *
566
- * Auto-provisions a fresh local-private-key signer via
567
- * `SignerProvisioner` when the backend signals `bootstrap_required`.
568
- * Pass an optional `signer` to override auto-provisioning (used by
569
- * test harnesses that pre-build a deterministic actor).
575
+ * OTP verification is intentionally branch-explicit: existing members
576
+ * authenticate immediately, while first-run or incomplete accounts stop
577
+ * at `bootstrapRequired` until the caller chooses a username and calls
578
+ * `completeBootstrap()`.
579
+ *
580
+ * `completeBootstrap()` auto-provisions a fresh local-private-key signer
581
+ * via `SignerProvisioner` unless the caller passes or configures an
582
+ * explicit signer. Test harnesses use that override to keep the actor's
583
+ * signer stable across later dogfooding operations.
570
584
  */
571
585
  declare function useAuth(options?: UseAuthOptions): UseAuthResult;
572
586
 
573
- /**
574
- * @deprecated Use `useAuth()` instead. `useAuthFlow` will be removed
575
- * in a future release.
576
- */
577
- declare function useAuthFlow(): {
578
- readonly snapshot: xstate.MachineSnapshot<any, any, any, any, any, any, any, any>;
579
- readonly send: (event: any) => void;
580
- };
581
- /**
582
- * @deprecated Use `useAuth()` instead. `useAuthBootstrapFlow` will be
583
- * removed in a future release.
584
- */
585
- declare function useAuthBootstrapFlow(): {
586
- readonly snapshot: xstate.MachineSnapshot<_capxul_sdk.AuthBootstrapFlowContext, {
587
- readonly type: "ENTER_EMAIL";
588
- readonly email: Email;
589
- } | {
590
- readonly type: "REQUEST_OTP";
591
- } | {
592
- readonly type: "ENTER_OTP";
593
- readonly code: string;
594
- } | {
595
- readonly type: "VERIFY_OTP";
596
- } | {
597
- readonly type: "ENTER_USERNAME";
598
- readonly username: Username;
599
- } | {
600
- readonly type: "COMPLETE_BOOTSTRAP";
601
- } | {
602
- readonly type: "BACK";
603
- } | {
604
- readonly type: "RESET";
605
- } | {
606
- readonly type: "SIGN_OUT";
607
- }, {
608
- [x: string]: xstate.ActorRefFromLogic<xstate.PromiseActorLogic<void, void, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<_capxul_sdk.CompleteBootstrapResult, {
609
- bootstrapToken: _capxul_sdk.AuthBootstrapToken;
610
- username: Username;
611
- }, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<void, {
612
- email: Email;
613
- }, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<_capxul_sdk.VerifyOtpResult, {
614
- email: Email;
615
- code: string;
616
- }, xstate.EventObject>> | undefined;
617
- }, "email" | "error" | "bootstrap_required" | "authenticated" | "sending_otp" | "otp_requested" | "signing_out" | "verifying_otp" | "completing_bootstrap", string, xstate.NonReducibleUnknown, xstate.MetaObject, {
618
- id: "authBootstrap";
619
- states: {
620
- readonly email: {};
621
- readonly sending_otp: {};
622
- readonly otp_requested: {};
623
- readonly verifying_otp: {};
624
- readonly bootstrap_required: {};
625
- readonly completing_bootstrap: {};
626
- readonly authenticated: {};
627
- readonly signing_out: {};
628
- readonly error: {};
629
- };
630
- }>;
631
- readonly send: (event: {
632
- readonly type: "ENTER_EMAIL";
633
- readonly email: Email;
634
- } | {
635
- readonly type: "REQUEST_OTP";
636
- } | {
637
- readonly type: "ENTER_OTP";
638
- readonly code: string;
639
- } | {
640
- readonly type: "VERIFY_OTP";
641
- } | {
642
- readonly type: "ENTER_USERNAME";
643
- readonly username: Username;
644
- } | {
645
- readonly type: "COMPLETE_BOOTSTRAP";
646
- } | {
647
- readonly type: "BACK";
648
- } | {
649
- readonly type: "RESET";
650
- } | {
651
- readonly type: "SIGN_OUT";
652
- }) => void;
653
- };
654
587
  declare function useOnboardingFlow(): {
655
588
  readonly snapshot: xstate.MachineSnapshot<any, any, any, any, any, any, any, any>;
656
589
  readonly send: (event: any) => void;
@@ -737,4 +670,4 @@ declare function injectedConnector(options?: InjectedConnectorOptions): CapxulCo
737
670
  */
738
671
  declare function localPrivateKeyConnector(options: LocalPrivateKeyConnectorOptions): CapxulConnector;
739
672
 
740
- export { type AuthState, CapxulClientProvider, type CapxulClientProviderProps, type CapxulConnector, type CapxulConnectorKind, type CapxulConnectorSession, CapxulProvider, type CapxulProviderProps, CapxulTransportProvider, type CapxulTransportProviderProps, type DocumentsFilters, type InjectedConnectorOptions, type LocalPrivateKeyConnectorOptions, type OrgDocumentsFilters, type OrgScopedFilters, type OwnerRef, type OwnerScopedFilters, type PaginationFilters, type PaymentsFilters, type QueryResult, type TransfersFilters, type UseApiKeyArgs, type UseAuthOptions, type UseAuthResult, type UseBalanceLedgerEntryArgs, type UseExternalAccountArgs, type UseMemberArgs, type UseTokenTransferArgs, type User, type VirtualAccountsFilters, type VirtualCardsFilters, type WithdrawalsFilters, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useAuthBootstrapFlow, useAuthFlow, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
673
+ export { type AuthState, type AuthVerifyResult, type BootstrapContinuation, CapxulClientProvider, type CapxulClientProviderProps, type CapxulConnector, type CapxulConnectorKind, type CapxulConnectorSession, CapxulProvider, type CapxulProviderProps, CapxulTransportProvider, type CapxulTransportProviderProps, type DocumentsFilters, type InjectedConnectorOptions, type LocalPrivateKeyConnectorOptions, type OrgDocumentsFilters, type OrgScopedFilters, type OwnerRef, type OwnerScopedFilters, type PaginationFilters, type PaymentsFilters, type QueryResult, type TransfersFilters, type UseApiKeyArgs, type UseAuthOptions, type UseAuthResult, type UseBalanceLedgerEntryArgs, type UseExternalAccountArgs, type UseMemberArgs, type UseTokenTransferArgs, type User, type VirtualAccountsFilters, type VirtualCardsFilters, type WithdrawalsFilters, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
- import * as _capxul_sdk from '@capxul/sdk';
3
- import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, MembershipStatus, TokenTransfersListInput, TokenTransfersListPage, MemberInviteResponse, Session } from '@capxul/sdk';
4
- export { AuthBootstrapFlowContext, AuthBootstrapFlowEvent, AuthFlowContext, AuthFlowEvent, BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
2
+ import { HttpTransport, TransportState, BrowserCapxulConfig, AuthSessionStore, AccountId, OrganizationId, ApiKeyId, BalanceLedgerEntryId, ExternalAccountId, MemberId, Account, ApiKey, BalanceLedgerEntry, DocumentId, Document, ExternalAccount, KycProfile, CapxulError as CapxulError$1, Member, OperationId, Operation, Organization, PaymentId, Payment, SafeId, Safe, SubAccountId, SubAccount, TokenTransfer, TransferId, Transfer, Treasury, VirtualAccountId, VirtualAccount, VirtualCardId, VirtualCard, WebhookEndpointId, WebhookEndpoint, WebhookEventId, WebhookEvent, WithdrawalId, Withdrawal, List, MembershipStatus, TokenTransfersListInput, TokenTransfersListPage, MemberInviteResponse, Session, AuthBootstrapToken, AuthBootstrapReason } from '@capxul/sdk';
3
+ export { BrowserCapxulConfig, OnboardingFlowContext, OnboardingFlowEvent, ProvisioningFlowContext, ProvisioningFlowEvent } from '@capxul/sdk';
5
4
  import { QueryClient, UseQueryResult, UseMutationResult } from '@tanstack/react-query';
6
5
  import { CapxulClient } from '@capxul/sdk/client';
7
6
  import { CapxulError } from '@capxul/sdk/errors';
@@ -517,14 +516,6 @@ declare function useRevokeMember(): UseMutationResult<Member, CapxulError$1, Rev
517
516
  declare function useRemoveMember(): UseMutationResult<void, CapxulError$1, RemoveMemberArgs>;
518
517
  declare function useResendInvitation(): UseMutationResult<MemberInviteResponse, CapxulError$1, ResendInvitationArgs>;
519
518
 
520
- /**
521
- * Lowercased, shape-validated email address. Brand prevents swapping
522
- * with `phoneNumber`, `username`, or other string identifiers (per
523
- * `CANON.md` §4.54 and `sdk-surface.md` §5h).
524
- */
525
- type Email = string & {
526
- readonly __capxulEmailBrand: "Email";
527
- };
528
519
  /**
529
520
  * Public Capxul username. 3–30 chars, letter-first, lowercased,
530
521
  * remaining chars from `[a-z0-9_-]` (per `CANON.md` §4.54 and
@@ -543,13 +534,31 @@ type User = {
543
534
  readonly safe: Safe;
544
535
  readonly session: Session;
545
536
  };
546
- type AuthState = "idle" | "sendingOtp" | "awaitingOtp" | "bootstrapping" | "authenticated" | "error";
537
+ type AuthState = "idle" | "sendingOtp" | "awaitingOtp" | "bootstrapRequired" | "provisioningSigner" | "bootstrapping" | "authenticated" | "error";
538
+ type BootstrapContinuation = {
539
+ readonly bootstrapToken: AuthBootstrapToken;
540
+ readonly email: string;
541
+ readonly reason: AuthBootstrapReason;
542
+ readonly username?: Username;
543
+ readonly session: Session;
544
+ };
545
+ type AuthVerifyResult = {
546
+ readonly kind: "existing_member";
547
+ readonly session: Session;
548
+ readonly user: User;
549
+ } | {
550
+ readonly kind: "bootstrap_required";
551
+ readonly session: Session;
552
+ readonly bootstrap: BootstrapContinuation;
553
+ };
547
554
  type UseAuthResult = {
548
555
  readonly state: AuthState;
549
556
  readonly user: User | null;
557
+ readonly bootstrap: BootstrapContinuation | null;
550
558
  readonly error: Error | null;
551
559
  readonly signIn: (email: string) => Promise<void>;
552
- readonly verifyOtp: (email: string, otp: string) => Promise<Session>;
560
+ readonly verifyOtp: (email: string, otp: string) => Promise<AuthVerifyResult>;
561
+ readonly completeBootstrap: (username: Username, signer?: Account$1) => Promise<User>;
553
562
  readonly signOut: () => Promise<void>;
554
563
  };
555
564
  type UseAuthOptions = {
@@ -557,100 +566,24 @@ type UseAuthOptions = {
557
566
  readonly signer?: Account$1;
558
567
  };
559
568
  /**
560
- * Canonical auth hook — replaces `useAuthFlow` and `useAuthBootstrapFlow`.
569
+ * Canonical auth hook.
561
570
  *
562
571
  * Reactive state (`state`, `user`, `error`) is suitable for UI
563
572
  * observers; `signIn`, `verifyOtp`, and `signOut` return promises so
564
573
  * the reference CLI can drive the flow imperatively.
565
574
  *
566
- * Auto-provisions a fresh local-private-key signer via
567
- * `SignerProvisioner` when the backend signals `bootstrap_required`.
568
- * Pass an optional `signer` to override auto-provisioning (used by
569
- * test harnesses that pre-build a deterministic actor).
575
+ * OTP verification is intentionally branch-explicit: existing members
576
+ * authenticate immediately, while first-run or incomplete accounts stop
577
+ * at `bootstrapRequired` until the caller chooses a username and calls
578
+ * `completeBootstrap()`.
579
+ *
580
+ * `completeBootstrap()` auto-provisions a fresh local-private-key signer
581
+ * via `SignerProvisioner` unless the caller passes or configures an
582
+ * explicit signer. Test harnesses use that override to keep the actor's
583
+ * signer stable across later dogfooding operations.
570
584
  */
571
585
  declare function useAuth(options?: UseAuthOptions): UseAuthResult;
572
586
 
573
- /**
574
- * @deprecated Use `useAuth()` instead. `useAuthFlow` will be removed
575
- * in a future release.
576
- */
577
- declare function useAuthFlow(): {
578
- readonly snapshot: xstate.MachineSnapshot<any, any, any, any, any, any, any, any>;
579
- readonly send: (event: any) => void;
580
- };
581
- /**
582
- * @deprecated Use `useAuth()` instead. `useAuthBootstrapFlow` will be
583
- * removed in a future release.
584
- */
585
- declare function useAuthBootstrapFlow(): {
586
- readonly snapshot: xstate.MachineSnapshot<_capxul_sdk.AuthBootstrapFlowContext, {
587
- readonly type: "ENTER_EMAIL";
588
- readonly email: Email;
589
- } | {
590
- readonly type: "REQUEST_OTP";
591
- } | {
592
- readonly type: "ENTER_OTP";
593
- readonly code: string;
594
- } | {
595
- readonly type: "VERIFY_OTP";
596
- } | {
597
- readonly type: "ENTER_USERNAME";
598
- readonly username: Username;
599
- } | {
600
- readonly type: "COMPLETE_BOOTSTRAP";
601
- } | {
602
- readonly type: "BACK";
603
- } | {
604
- readonly type: "RESET";
605
- } | {
606
- readonly type: "SIGN_OUT";
607
- }, {
608
- [x: string]: xstate.ActorRefFromLogic<xstate.PromiseActorLogic<void, void, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<_capxul_sdk.CompleteBootstrapResult, {
609
- bootstrapToken: _capxul_sdk.AuthBootstrapToken;
610
- username: Username;
611
- }, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<void, {
612
- email: Email;
613
- }, xstate.EventObject>> | xstate.ActorRefFromLogic<xstate.PromiseActorLogic<_capxul_sdk.VerifyOtpResult, {
614
- email: Email;
615
- code: string;
616
- }, xstate.EventObject>> | undefined;
617
- }, "email" | "error" | "bootstrap_required" | "authenticated" | "sending_otp" | "otp_requested" | "signing_out" | "verifying_otp" | "completing_bootstrap", string, xstate.NonReducibleUnknown, xstate.MetaObject, {
618
- id: "authBootstrap";
619
- states: {
620
- readonly email: {};
621
- readonly sending_otp: {};
622
- readonly otp_requested: {};
623
- readonly verifying_otp: {};
624
- readonly bootstrap_required: {};
625
- readonly completing_bootstrap: {};
626
- readonly authenticated: {};
627
- readonly signing_out: {};
628
- readonly error: {};
629
- };
630
- }>;
631
- readonly send: (event: {
632
- readonly type: "ENTER_EMAIL";
633
- readonly email: Email;
634
- } | {
635
- readonly type: "REQUEST_OTP";
636
- } | {
637
- readonly type: "ENTER_OTP";
638
- readonly code: string;
639
- } | {
640
- readonly type: "VERIFY_OTP";
641
- } | {
642
- readonly type: "ENTER_USERNAME";
643
- readonly username: Username;
644
- } | {
645
- readonly type: "COMPLETE_BOOTSTRAP";
646
- } | {
647
- readonly type: "BACK";
648
- } | {
649
- readonly type: "RESET";
650
- } | {
651
- readonly type: "SIGN_OUT";
652
- }) => void;
653
- };
654
587
  declare function useOnboardingFlow(): {
655
588
  readonly snapshot: xstate.MachineSnapshot<any, any, any, any, any, any, any, any>;
656
589
  readonly send: (event: any) => void;
@@ -737,4 +670,4 @@ declare function injectedConnector(options?: InjectedConnectorOptions): CapxulCo
737
670
  */
738
671
  declare function localPrivateKeyConnector(options: LocalPrivateKeyConnectorOptions): CapxulConnector;
739
672
 
740
- export { type AuthState, CapxulClientProvider, type CapxulClientProviderProps, type CapxulConnector, type CapxulConnectorKind, type CapxulConnectorSession, CapxulProvider, type CapxulProviderProps, CapxulTransportProvider, type CapxulTransportProviderProps, type DocumentsFilters, type InjectedConnectorOptions, type LocalPrivateKeyConnectorOptions, type OrgDocumentsFilters, type OrgScopedFilters, type OwnerRef, type OwnerScopedFilters, type PaginationFilters, type PaymentsFilters, type QueryResult, type TransfersFilters, type UseApiKeyArgs, type UseAuthOptions, type UseAuthResult, type UseBalanceLedgerEntryArgs, type UseExternalAccountArgs, type UseMemberArgs, type UseTokenTransferArgs, type User, type VirtualAccountsFilters, type VirtualCardsFilters, type WithdrawalsFilters, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useAuthBootstrapFlow, useAuthFlow, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
673
+ export { type AuthState, type AuthVerifyResult, type BootstrapContinuation, CapxulClientProvider, type CapxulClientProviderProps, type CapxulConnector, type CapxulConnectorKind, type CapxulConnectorSession, CapxulProvider, type CapxulProviderProps, CapxulTransportProvider, type CapxulTransportProviderProps, type DocumentsFilters, type InjectedConnectorOptions, type LocalPrivateKeyConnectorOptions, type OrgDocumentsFilters, type OrgScopedFilters, type OwnerRef, type OwnerScopedFilters, type PaginationFilters, type PaymentsFilters, type QueryResult, type TransfersFilters, type UseApiKeyArgs, type UseAuthOptions, type UseAuthResult, type UseBalanceLedgerEntryArgs, type UseExternalAccountArgs, type UseMemberArgs, type UseTokenTransferArgs, type User, type VirtualAccountsFilters, type VirtualCardsFilters, type WithdrawalsFilters, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { createContext, useContext, useSyncExternalStore, useMemo, useEffect, useState, useCallback } from 'react';
3
- import { makeHttpTransport, createCapxulClient, AuthService, CapxulError as CapxulError$1, SignerProvisioner, toUsername } from '@capxul/sdk';
2
+ import { createContext, useContext, useSyncExternalStore, useMemo, useEffect, useState, useRef, useCallback } from 'react';
3
+ import { makeHttpTransport, createCapxulClient, AuthService, CapxulError as CapxulError$1, SignerProvisioner } from '@capxul/sdk';
4
4
  import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
  import { ConvexReactClient } from 'convex/react';
@@ -857,12 +857,62 @@ function useResendInvitation() {
857
857
  }
858
858
  });
859
859
  }
860
+
861
+ // ../observability/src/debug-log.ts
862
+ function isDevelopmentBuild() {
863
+ if (typeof process === "undefined") {
864
+ return false;
865
+ }
866
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
867
+ }
868
+ function debugLog(line) {
869
+ if (!isDevelopmentBuild()) return;
870
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
871
+ console.info(line);
872
+ return;
873
+ }
874
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
875
+ process.stderr.write(`${line}
876
+ `);
877
+ }
878
+ }
879
+ function formatDebugValue(value) {
880
+ if (value === void 0 || value === "") return "";
881
+ if (typeof value === "string") return value;
882
+ try {
883
+ return JSON.stringify(value);
884
+ } catch {
885
+ return String(value);
886
+ }
887
+ }
888
+ function track(...args) {
889
+ const [name, props] = args;
890
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
891
+ }
892
+ function formatDebugValue2(value) {
893
+ if (value === void 0 || value === "") return "";
894
+ if (typeof value === "string") return value;
895
+ try {
896
+ return JSON.stringify(value);
897
+ } catch {
898
+ return String(value);
899
+ }
900
+ }
901
+ function identify(userId, traits) {
902
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
903
+ }
904
+ function resetIdentity() {
905
+ }
860
906
  function useAuth(options) {
861
907
  const authService = useAuthService();
862
908
  const signerProvisioner = useMemoizedSignerProvisioner();
863
909
  const injectedSigner = options?.signer;
864
910
  const [state, setState] = useState("idle");
865
911
  const [user, setUser] = useState(null);
912
+ const [bootstrap, setBootstrap] = useState(
913
+ null
914
+ );
915
+ const bootstrapRef = useRef(null);
866
916
  const [error, setError] = useState(null);
867
917
  const signIn = useCallback(
868
918
  async (email) => {
@@ -870,9 +920,13 @@ function useAuth(options) {
870
920
  setError(null);
871
921
  try {
872
922
  await authService.sendOtp(email);
923
+ track("auth_otp_requested", {
924
+ email_domain: emailDomain(email)
925
+ });
873
926
  setState("awaitingOtp");
874
927
  } catch (err) {
875
928
  const wrapped = err instanceof Error ? err : new Error(String(err));
929
+ trackAuthFailure("otp_request", wrapped);
876
930
  setError(wrapped);
877
931
  setState("error");
878
932
  throw wrapped;
@@ -887,25 +941,96 @@ function useAuth(options) {
887
941
  try {
888
942
  const result = await authService.verifyOtp(email, otp);
889
943
  if (result.kind === "existing_member") {
890
- const nextUser2 = {
944
+ track("auth_otp_verified", {
945
+ email_domain: emailDomain(result.session.email),
946
+ branch: "existing_member"
947
+ });
948
+ const nextUser = {
891
949
  account: result.account,
892
950
  username: result.username,
893
951
  safe: result.safe,
894
952
  session: result.session
895
953
  };
896
- setUser(nextUser2);
954
+ setUser(nextUser);
955
+ setBootstrap(null);
956
+ bootstrapRef.current = null;
957
+ identify(result.session.authUserId, {
958
+ auth_branch: "existing_member"
959
+ });
960
+ track("auth_session_ready", {
961
+ branch: "existing_member",
962
+ has_convex_jwt: typeof result.session.convexJwt === "string"
963
+ });
897
964
  setState("authenticated");
898
- return result.session;
965
+ return {
966
+ kind: "existing_member",
967
+ session: result.session,
968
+ user: nextUser
969
+ };
899
970
  }
971
+ const nextBootstrap = {
972
+ bootstrapToken: result.bootstrapToken,
973
+ email: result.email,
974
+ reason: result.reason,
975
+ username: result.username,
976
+ session: result.session
977
+ };
978
+ setUser(null);
979
+ setBootstrap(nextBootstrap);
980
+ bootstrapRef.current = nextBootstrap;
981
+ track("auth_otp_verified", {
982
+ email_domain: emailDomain(result.session.email),
983
+ branch: "bootstrap_required"
984
+ });
985
+ track("auth_bootstrap_required", {
986
+ reason: result.reason,
987
+ has_suggested_username: result.username !== void 0,
988
+ has_convex_jwt: typeof result.session.convexJwt === "string"
989
+ });
990
+ setState("bootstrapRequired");
991
+ return {
992
+ kind: "bootstrap_required",
993
+ session: result.session,
994
+ bootstrap: nextBootstrap
995
+ };
996
+ } catch (err) {
997
+ const wrapped = err instanceof Error ? err : new Error(String(err));
998
+ trackAuthFailure("otp_verify", wrapped);
999
+ setError(wrapped);
1000
+ setState("error");
1001
+ throw wrapped;
1002
+ }
1003
+ },
1004
+ [authService]
1005
+ );
1006
+ const completeBootstrap = useCallback(
1007
+ async (username, signer) => {
1008
+ const pendingBootstrap = bootstrapRef.current;
1009
+ if (!pendingBootstrap) {
1010
+ const missing = new Error(
1011
+ "completeBootstrap requires a prior bootstrap_required OTP result."
1012
+ );
1013
+ track("auth_session_lost", { step: "complete_bootstrap" });
1014
+ setError(missing);
1015
+ setState("error");
1016
+ throw missing;
1017
+ }
1018
+ setError(null);
1019
+ track("auth_username_submitted", {
1020
+ has_suggested_username: pendingBootstrap.username !== void 0
1021
+ });
1022
+ setState("provisioningSigner");
1023
+ try {
1024
+ const signerKind = signer ? "provided" : injectedSigner ? "configured" : "generated";
1025
+ const selectedSigner = signer ?? injectedSigner ?? signerProvisioner.provision().signer;
1026
+ track("auth_signer_provisioned", { signer_kind: signerKind });
900
1027
  setState("bootstrapping");
901
- const signer = injectedSigner ?? signerProvisioner.provision().signer;
902
- const username = result.username ?? generateUsernameFromEmail(result.email);
903
1028
  const bootstrapResult = await authService.completeBootstrap(
904
1029
  {
905
- bootstrapToken: result.bootstrapToken,
1030
+ bootstrapToken: pendingBootstrap.bootstrapToken,
906
1031
  username
907
1032
  },
908
- signer
1033
+ selectedSigner
909
1034
  );
910
1035
  const nextUser = {
911
1036
  account: bootstrapResult.account,
@@ -913,26 +1038,47 @@ function useAuth(options) {
913
1038
  safe: bootstrapResult.safe,
914
1039
  session: bootstrapResult.session
915
1040
  };
1041
+ track("auth_safe_provisioned", {
1042
+ safe_status: bootstrapResult.safe.status
1043
+ });
916
1044
  setUser(nextUser);
1045
+ setBootstrap(null);
1046
+ bootstrapRef.current = null;
1047
+ identify(bootstrapResult.session.authUserId, {
1048
+ auth_branch: "bootstrap_required"
1049
+ });
1050
+ track("auth_bootstrap_completed", {
1051
+ reason: pendingBootstrap.reason
1052
+ });
1053
+ track("auth_session_ready", {
1054
+ branch: "bootstrap_required",
1055
+ has_convex_jwt: typeof bootstrapResult.session.convexJwt === "string"
1056
+ });
917
1057
  setState("authenticated");
918
- return bootstrapResult.session;
1058
+ return nextUser;
919
1059
  } catch (err) {
920
1060
  const wrapped = err instanceof Error ? err : new Error(String(err));
1061
+ trackAuthFailure("complete_bootstrap", wrapped);
921
1062
  setError(wrapped);
922
1063
  setState("error");
923
1064
  throw wrapped;
924
1065
  }
925
1066
  },
926
- [authService, signerProvisioner, injectedSigner]
1067
+ [authService, injectedSigner, signerProvisioner]
927
1068
  );
928
1069
  const signOut = useCallback(async () => {
929
1070
  setError(null);
930
1071
  try {
931
1072
  await authService.signOut();
1073
+ track("auth_signed_out");
1074
+ resetIdentity();
932
1075
  setUser(null);
1076
+ setBootstrap(null);
1077
+ bootstrapRef.current = null;
933
1078
  setState("idle");
934
1079
  } catch (err) {
935
1080
  const wrapped = err instanceof Error ? err : new Error(String(err));
1081
+ trackAuthFailure("sign_out", wrapped);
936
1082
  setError(wrapped);
937
1083
  setState("error");
938
1084
  throw wrapped;
@@ -942,48 +1088,46 @@ function useAuth(options) {
942
1088
  () => ({
943
1089
  state,
944
1090
  user,
1091
+ bootstrap,
945
1092
  error,
946
1093
  signIn,
947
1094
  verifyOtp,
1095
+ completeBootstrap,
948
1096
  signOut
949
1097
  }),
950
- [state, user, error, signIn, verifyOtp, signOut]
1098
+ [
1099
+ state,
1100
+ user,
1101
+ bootstrap,
1102
+ error,
1103
+ signIn,
1104
+ verifyOtp,
1105
+ completeBootstrap,
1106
+ signOut
1107
+ ]
951
1108
  );
952
1109
  }
953
1110
  function useMemoizedSignerProvisioner() {
954
1111
  const [provisioner] = useState(() => new SignerProvisioner());
955
1112
  return provisioner;
956
1113
  }
957
- function generateUsernameFromEmail(email) {
958
- const local = email.split("@")[0]?.toLowerCase() ?? "user";
959
- let sanitized = local.replace(/[^a-z0-9_-]/g, "_");
960
- if (/^[0-9]/.test(sanitized)) {
961
- sanitized = "u" + sanitized;
962
- }
963
- if (sanitized.length < 3) {
964
- sanitized = sanitized + "_".repeat(3 - sanitized.length);
965
- }
966
- if (sanitized.length > 30) {
967
- sanitized = sanitized.slice(0, 30);
968
- }
969
- try {
970
- return toUsername(sanitized);
971
- } catch {
972
- const fallback = `user_${Date.now() % 1e6}`;
973
- return toUsername(fallback);
974
- }
1114
+ function emailDomain(email) {
1115
+ const domain = email.split("@")[1]?.trim().toLowerCase();
1116
+ return domain && /^[a-z0-9.-]+$/.test(domain) ? domain : "unknown";
975
1117
  }
976
- function useAuthFlow() {
977
- const client = useCapxul();
978
- const machine = useMemo(() => client.flows.auth(), [client]);
979
- const [snapshot, send] = useActor(machine);
980
- return { snapshot, send };
1118
+ function trackAuthFailure(step, error) {
1119
+ const reason = errorCode(error);
1120
+ if (reason === "PERMISSION_DENIED" || reason === "AUTHZ_DENIED") {
1121
+ track("authz_denied", { step, reason });
1122
+ }
1123
+ track("auth_failed", {
1124
+ step,
1125
+ reason
1126
+ });
981
1127
  }
982
- function useAuthBootstrapFlow() {
983
- const client = useCapxul();
984
- const machine = useMemo(() => client.flows.authBootstrap(), [client]);
985
- const [snapshot, send] = useActor(machine);
986
- return { snapshot, send };
1128
+ function errorCode(error) {
1129
+ const code = error.code;
1130
+ return typeof code === "string" && code.length > 0 ? code : error.name || "Error";
987
1131
  }
988
1132
  function useOnboardingFlow() {
989
1133
  const client = useCapxul();
@@ -1083,4 +1227,4 @@ function validateAndNormalizeEvmAddress(field, raw) {
1083
1227
  }
1084
1228
  }
1085
1229
 
1086
- export { CapxulClientProvider, CapxulProvider, CapxulTransportProvider, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useAuthBootstrapFlow, useAuthFlow, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
1230
+ export { CapxulClientProvider, CapxulProvider, CapxulTransportProvider, createCapxulConfig, injectedConnector, localPrivateKeyConnector, useAcceptInvitation, useAccount, useAccountBalanceLedger, useApiKey, useApiKeys, useAuth, useBalanceLedger, useBalanceLedgerEntry, useCapxul, useCapxulStatus, useDocument, useDocuments, useExternalAccount, useExternalAccounts, useInviteMember, useKycProfile, useMe, useMember, useMembers, useOnboardingFlow, useOperation, useOrgBalanceLedger, useOrgDocuments, useOrgPayments, useOrgTransfers, useOrgWithdrawals, useOrganization, useOrganizations, usePayment, usePayments, useProvisioningFlow, useRemoveMember, useResendInvitation, useRevokeMember, useSafe, useSubAccount, useSubAccounts, useTokenTransfer, useTokenTransfers, useTransfer, useTransfers, useTreasury, useUpdateMemberRole, useVirtualAccount, useVirtualAccounts, useVirtualCard, useVirtualCards, useWebhookEndpoint, useWebhookEndpoints, useWebhookEvent, useWithdrawal, useWithdrawals };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sdk-react",
3
- "version": "0.2.0-alpha.3",
3
+ "version": "0.2.0-alpha.4",
4
4
  "description": "React provider + hooks for the @capxul/sdk headless client.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@capxul/sdk": "0.2.0-alpha.3"
49
+ "@capxul/sdk": "0.2.0-alpha.4"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@tanstack/react-query": "^5",