@capxul/sdk-react 1.0.0-alpha.12 → 1.0.0-alpha.14

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/index.mjs CHANGED
@@ -3,8 +3,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
3
3
  import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { captureExceptionSync, createCapxulClient, isSettingUpLifecycle } from "@capxul/sdk";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { Errors, isCapxulError } from "@capxul/errors";
7
- import "@capxul/types";
8
6
  //#region src/internal/capxul-bootstrap-context.tsx
9
7
  const CapxulBootstrapContext = createContext(null);
10
8
  function CapxulBootstrapProvider({ value, children }) {
@@ -149,6 +147,159 @@ function CapxulProvider(props) {
149
147
  });
150
148
  }
151
149
  //#endregion
150
+ //#region ../errors/src/errors.ts
151
+ const CAPXUL_ERROR_CODES = [
152
+ "NOT_AUTHENTICATED",
153
+ "EMAIL_DELIVERY_FAILED",
154
+ "PROFILE_NOT_FOUND",
155
+ "SMART_ACCOUNT_MISSING",
156
+ "PLAYER_NOT_FOUND",
157
+ "ACCOUNT_NOT_FOUND",
158
+ "PROVIDER_ERROR",
159
+ "INVALID_INPUT",
160
+ "ENV_MISSING",
161
+ "NOT_IMPLEMENTED",
162
+ "VERIFICATION_REQUIRED",
163
+ "INSUFFICIENT_BALANCE",
164
+ "INVALID_RECIPIENT",
165
+ "ROLE_PERMISSION_DENIED",
166
+ "TRANSACTION_FAILED",
167
+ "RATE_LIMITED",
168
+ "NETWORK_ERROR",
169
+ "UNKNOWN",
170
+ "OTP_EXPIRED",
171
+ "SIGNER_REJECTED",
172
+ "CANCELLED",
173
+ "WRONG_STATE"
174
+ ];
175
+ var CapxulError$1 = class extends Error {
176
+ code;
177
+ details;
178
+ correlationId;
179
+ layer;
180
+ constructor(code, message, options = {}) {
181
+ super(message, "cause" in options ? { cause: options.cause } : void 0);
182
+ this.name = "CapxulError";
183
+ this.code = code;
184
+ if (options.details !== void 0) this.details = options.details;
185
+ if (options.correlationId !== void 0) this.correlationId = options.correlationId;
186
+ if (options.layer !== void 0) this.layer = options.layer;
187
+ }
188
+ };
189
+ function isCapxulError(value) {
190
+ return value instanceof CapxulError$1;
191
+ }
192
+ const Errors = {
193
+ notAuthenticated: (message, opts) => new CapxulError$1("NOT_AUTHENTICATED", message ?? "Not authenticated", opts?.failure_mode ? { details: { failure_mode: opts.failure_mode } } : void 0),
194
+ emailDeliveryFailed: (detail) => new CapxulError$1("EMAIL_DELIVERY_FAILED", "Failed to send email", { details: { detail } }),
195
+ profileNotFound: (authUserId) => new CapxulError$1("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`, { details: { authUserId } }),
196
+ smartAccountMissing: (authUserId) => new CapxulError$1("SMART_ACCOUNT_MISSING", "Smart account not provisioned", { details: { authUserId } }),
197
+ playerNotFound: (playerId) => new CapxulError$1("PLAYER_NOT_FOUND", playerId ? `Openfort player ${playerId} not found` : "Openfort player not found", playerId === void 0 ? void 0 : { details: { playerId } }),
198
+ accountNotFound: (accountId) => new CapxulError$1("ACCOUNT_NOT_FOUND", accountId ? `Openfort account ${accountId} not found` : "Openfort account not found", accountId === void 0 ? void 0 : { details: { accountId } }),
199
+ providerError: (provider, operation, cause, opts) => {
200
+ const details = {
201
+ provider,
202
+ operation
203
+ };
204
+ if (opts?.failure_mode) details.failure_mode = opts.failure_mode;
205
+ return new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation}`, {
206
+ cause,
207
+ details
208
+ });
209
+ },
210
+ invalidInput: (field, reason) => new CapxulError$1("INVALID_INPUT", `Invalid ${field}: ${reason}`, { details: {
211
+ field,
212
+ reason
213
+ } }),
214
+ envMissing: (name) => new CapxulError$1("ENV_MISSING", `Environment variable ${name} not configured`, { details: { name } }),
215
+ notImplemented: (domain, method) => new CapxulError$1("NOT_IMPLEMENTED", `${domain}.${method} is not yet implemented. This feature is planned for a future release.`, { details: {
216
+ domain,
217
+ method
218
+ } }),
219
+ /**
220
+ * Sibling factory to {@link Errors.providerError} for the per-state timeout
221
+ * path in flows (initially ProvisioningFlow's mint_openfort / deploy_safe /
222
+ * register_indexer `after:` timers). Same `PROVIDER_ERROR` code as
223
+ * `providerError`, plus a `details.reason: "timeout"` discriminator so
224
+ * downstream observers can distinguish failure modes without parsing the
225
+ * message string. The redacted message names the timeout budget; the
226
+ * native `cause` carries the same information for `reportError` fidelity.
227
+ */
228
+ providerTimeout: (provider, operation, timeoutMs) => new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation} (timeout exceeded ${timeoutMs}ms)`, {
229
+ details: {
230
+ provider,
231
+ operation,
232
+ reason: "timeout"
233
+ },
234
+ cause: /* @__PURE__ */ new Error(`timeout: ${operation} exceeded ${timeoutMs}ms`)
235
+ }),
236
+ verificationRequired: (details) => {
237
+ return new CapxulError$1("VERIFICATION_REQUIRED", "rail" in details ? `Verification is required before ${details.rail} can use ${details.currentKind}.` : `Verification tier ${details.requiredTier} is required.`, { details });
238
+ },
239
+ insufficientBalance: (asset, available, required) => new CapxulError$1("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, { details: {
240
+ asset,
241
+ available,
242
+ required
243
+ } }),
244
+ invalidRecipient: (reason) => new CapxulError$1("INVALID_RECIPIENT", `Invalid recipient: ${reason}`, { details: { reason } }),
245
+ /**
246
+ * The org Zodiac Roles modifier REFUSED the spend on-chain (G4 · #547): the
247
+ * member's role condition (per-tx cap, per-day allowance, allowed recipient,
248
+ * or membership) was violated, so `execTransactionWithRole` reverted. This is
249
+ * a PERMISSION denial — explicitly NOT an `INSUFFICIENT_BALANCE` (the treasury
250
+ * held the funds; the role's authority is what bound). `reason` discriminates
251
+ * the violated condition (`over_cap` / `daily_cap` / `not_member` /
252
+ * `disallowed_recipient` / `condition_violation`); leak-safe — no on-chain
253
+ * identifiers ever enter the details.
254
+ */
255
+ rolePermissionDenied: (details) => new CapxulError$1("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: details.operation === void 0 ? { reason: details.reason } : {
256
+ reason: details.reason,
257
+ operation: details.operation
258
+ } }),
259
+ /**
260
+ * A transaction (or sponsored UserOp) failed. `details.reason` discriminates
261
+ * the failure mode for callers that must distinguish a CONFIRMED on-chain
262
+ * revert (`"onchain_revert"` — the op executed and reverted, e.g. a Zodiac
263
+ * Roles condition violation) from an inconclusive infra failure. A confirmed
264
+ * revert is the ONLY mode the org spend port may map to a roles denial.
265
+ */
266
+ transactionFailed: (operation, cause, extra) => new CapxulError$1("TRANSACTION_FAILED", `Transaction failed: ${operation}`, {
267
+ cause,
268
+ details: extra?.reason === void 0 ? { operation } : {
269
+ operation,
270
+ reason: extra.reason
271
+ }
272
+ }),
273
+ rateLimited: (details) => new CapxulError$1("RATE_LIMITED", "Rate limit exceeded", details === void 0 ? void 0 : { details: { ...details } }),
274
+ networkError: (operation, cause) => new CapxulError$1("NETWORK_ERROR", `Network error during ${operation}`, {
275
+ cause,
276
+ details: { operation }
277
+ }),
278
+ unknown: (cause) => new CapxulError$1("UNKNOWN", "Unknown error", { cause }),
279
+ otpExpired: (details) => new CapxulError$1("OTP_EXPIRED", "Verification code has expired. Request a new one.", details === void 0 ? void 0 : { details: { ...details } }),
280
+ signerRejected: (details) => new CapxulError$1("SIGNER_REJECTED", "Signer rejected the request.", {
281
+ cause: details.cause,
282
+ details: details.reason === void 0 ? { source: details.source } : {
283
+ source: details.source,
284
+ reason: details.reason
285
+ }
286
+ }),
287
+ cancelled: (details) => new CapxulError$1("CANCELLED", "Operation was cancelled.", details === void 0 ? void 0 : { details: { ...details } }),
288
+ /**
289
+ * Method called from a flow state where its precondition fails (TA16). The
290
+ * SDK's method API short-circuits with this error before driving the
291
+ * internal state machine. `currentState` is the Effect-machine snapshot
292
+ * tag (stringified — substrate is `@effect/experimental/Machine` per
293
+ * `docs/canon/decisions/state-machine-substrate.md`); `validStates`
294
+ * enumerates the states the method accepts.
295
+ */
296
+ wrongState: (details) => new CapxulError$1("WRONG_STATE", `${details.method} called from state '${details.currentState}'; valid states: ${details.validStates.join(", ")}`, { details: {
297
+ ...details,
298
+ validStates: [...details.validStates]
299
+ } })
300
+ };
301
+ new Set(CAPXUL_ERROR_CODES);
302
+ //#endregion
152
303
  //#region src/internal/require-bootstrapped-client.ts
153
304
  /**
154
305
  * Narrow the bootstrap-nullable client to a ready client inside a query /
@@ -659,12 +810,29 @@ async function invalidateMoneyState(queryClient, input) {
659
810
  await Promise.all(invalidations);
660
811
  }
661
812
  //#endregion
813
+ //#region src/internal/reject-unresolved-actor.ts
814
+ /**
815
+ * Guard for mutation hooks whose variables carry an optional `actor` field
816
+ * (`useCapxulPay`, `useCapxulPayout`, `useCapxulAddDestination`,
817
+ * `useCapxulRemoveDestination`). Mirrors `requireActorScope` on the query path:
818
+ * an EXPLICITLY-passed `actor: undefined` (the `capxulOrgScope(notYetLoadedOrgId)`
819
+ * footgun) must fail loudly rather than silently fall back to the personal
820
+ * scope. Omitting the `actor` key entirely keeps the personal-scope default.
821
+ *
822
+ * Throws `CapxulError` code `INVALID_INPUT` with `details.field === "actor"`;
823
+ * called inside an async mutationFn it surfaces as `mutation.error: CapxulError`.
824
+ */
825
+ function rejectUnresolvedActor(variables, operation) {
826
+ if (Object.hasOwn(variables, "actor") && variables.actor === void 0) throw Errors.invalidInput("actor", `explicitly provided but undefined for ${operation} — actor scope not yet resolved; omit actor for the personal scope`);
827
+ }
828
+ //#endregion
662
829
  //#region src/hooks/use-capxul-money.ts
663
830
  function useCapxulPay() {
664
831
  const client = useCapxulClientOrNull();
665
832
  const queryClient = useQueryClient();
666
833
  return useMutation({
667
834
  mutationFn: async (input) => {
835
+ rejectUnresolvedActor(input, "payments.pay");
668
836
  const bootstrappedClient = requireBootstrappedClient(client, "payments.pay");
669
837
  return unwrapCapxulResult(await bootstrappedClient.payments.pay(input), bootstrappedClient._internal.telemetry, "mutation");
670
838
  },
@@ -682,6 +850,7 @@ function useCapxulPayout() {
682
850
  const queryClient = useQueryClient();
683
851
  return useMutation({
684
852
  mutationFn: async (input) => {
853
+ rejectUnresolvedActor(input, "payments.payout");
685
854
  const bootstrappedClient = requireBootstrappedClient(client, "payments.payout");
686
855
  return unwrapCapxulResult(await bootstrappedClient.payments.payout(input), bootstrappedClient._internal.telemetry, "mutation");
687
856
  },
@@ -923,6 +1092,7 @@ function useCapxulAddDestination() {
923
1092
  const queryClient = useQueryClient();
924
1093
  return useMutation({
925
1094
  mutationFn: async (input) => {
1095
+ rejectUnresolvedActor(input, "destinations.add");
926
1096
  const bootstrappedClient = requireBootstrappedClient(client, "destinations.add");
927
1097
  return unwrapCapxulResult(await bootstrappedClient.destinations.add(input), bootstrappedClient._internal.telemetry, "mutation");
928
1098
  },
@@ -937,6 +1107,7 @@ function useCapxulRemoveDestination() {
937
1107
  const queryClient = useQueryClient();
938
1108
  return useMutation({
939
1109
  mutationFn: async (input) => {
1110
+ rejectUnresolvedActor(input, "destinations.remove");
940
1111
  const bootstrappedClient = requireBootstrappedClient(client, "destinations.remove");
941
1112
  return unwrapCapxulResult(await bootstrappedClient.destinations.remove(input), bootstrappedClient._internal.telemetry, "mutation");
942
1113
  },
@@ -1344,6 +1515,38 @@ function toQuerySlotState(query) {
1344
1515
  };
1345
1516
  }
1346
1517
  //#endregion
1518
+ //#region ../types/src/index.ts
1519
+ const SUPPORTED_CURRENCIES = [
1520
+ {
1521
+ code: "USD",
1522
+ symbol: "$",
1523
+ name: "US Dollar"
1524
+ },
1525
+ {
1526
+ code: "NGN",
1527
+ symbol: "NGN",
1528
+ name: "Nigerian Naira"
1529
+ },
1530
+ {
1531
+ code: "GHS",
1532
+ symbol: "GHS",
1533
+ name: "Ghanaian Cedi"
1534
+ },
1535
+ {
1536
+ code: "KES",
1537
+ symbol: "KSh",
1538
+ name: "Kenyan Shilling"
1539
+ },
1540
+ {
1541
+ code: "UGX",
1542
+ symbol: "USh",
1543
+ name: "Ugandan Shilling"
1544
+ }
1545
+ ];
1546
+ SUPPORTED_CURRENCIES.map((currency) => currency.code);
1547
+ Object.fromEntries(SUPPORTED_CURRENCIES.map((currency) => [currency.code, currency.symbol]));
1548
+ Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
1549
+ //#endregion
1347
1550
  //#region src/headless/money/SendMoney.tsx
1348
1551
  function SendMoney({ initialValue = null, slots, onSent }) {
1349
1552
  const pay = useCapxulPay();