@capxul/sdk 4.2.0-rc.7 → 4.2.0-rc.9

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.
@@ -1,4 +1,4 @@
1
- import { $ as toSessionToken, B as toDurationMs, C as ASSET_ID_RE, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAuthUserId, G as toJwtToken, H as toEpochMs, I as toBudgetId, J as toPartyId, K as toKycTier, L as toChainId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toRoleKey, R as toCountryCode, S as APP_ID_RE, St as redactUrlSecrets, T as EVM_ADDRESS_RE$1, U as toEpochSeconds, V as toEmail, W as toHandle, X as toPayrollRunId, Y as toPayrollGroupId, Z as toPublishableKey, _ as normalizeBindingEmail, _t as isFailureMode, at as CapxulError, bt as isCredentialField, c as AuthCachePortTag, ct as isCapxulError, d as authClientPortFromPromiseAdapter, dt as boundedResponseHeaders, et as toTesterKind, f as AuthClientError, ft as chainCauseProperties, gt as isChainUpstream, h as orgRoleKeyForLabel, ht as failureFingerprint, i as BrowserAuthCacheAdapter, it as CAPXUL_ERROR_CODES, j as toAccountId, l as SystemClockLayer, lt as CHAIN_UPSTREAMS, m as ADMIN_ROLE_LABEL, mt as decodeChainCause, ot as EXPECTED_OPERATION_OUTCOMES, p as AuthClientPortTag, pt as chainEvidenceLabel, q as toOrgId, r as InMemoryAuthCacheAdapter, rt as decodeConvexError, st as Errors, tt as validateHandle, u as ClockPortTag, ut as FAILURE_MODES$1, v as BASE_SEPOLIA_CHAIN_ID, vt as revertSummaryText, w as BYTES32_RE, x as ACCOUNT_ID_RE, xt as redactSecrets, y as deriveCapxulSafeAddress, yt as containsSensitiveMaterial, z as toCurrencyCode } from "./OAuthBearerAuthClient-hPwtSpEf.mjs";
1
+ import { $ as toRoleKey, B as toCurrencyCode, C as ASSET_ID_RE, Ct as redactSecrets, D as WEI_RE, E as SUPPORTED_CURRENCY_CODES, F as toAssetId, G as toHandle, H as toEmail, I as toAuthUserId, J as toOrgId, K as toJwtToken, L as toBudgetId, M as toAddress, N as toAllowedOrigin, O as ZERO_BYTES32, Q as toPublishableKey, R as toChainId, S as APP_ID_RE, St as isCredentialField, T as EVM_ADDRESS_RE$1, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, X as toPayrollGroupId, Y as toPartyId, Z as toPayrollRunId, _ as normalizeBindingEmail, _t as failureFingerprint, at as decodeConvexError, bt as revertSummaryText, c as AuthCachePortTag, ct as EXPECTED_OPERATION_OUTCOMES, d as authClientPortFromPromiseAdapter, dt as CHAIN_UPSTREAMS, et as toSessionToken, f as AuthClientError, ft as FAILURE_MODES$1, gt as decodeChainCause, h as orgRoleKeyForLabel, ht as chainEvidenceLabel, i as BrowserAuthCacheAdapter, j as toAccountId, l as SystemClockLayer, lt as Errors, m as ADMIN_ROLE_LABEL, mt as chainCauseProperties, nt as toTxHash, ot as CAPXUL_ERROR_CODES, p as AuthClientPortTag, pt as boundedResponseHeaders, q as toKycTier, r as InMemoryAuthCacheAdapter, rt as validateHandle, st as CapxulError, tt as toTesterKind, u as ClockPortTag, ut as isCapxulError, v as BASE_SEPOLIA_CHAIN_ID, vt as isChainUpstream, w as BYTES32_RE, wt as redactUrlSecrets, x as ACCOUNT_ID_RE, xt as containsSensitiveMaterial, y as deriveCapxulSafeAddress, yt as isFailureMode, z as toCountryCode } from "./OAuthBearerAuthClient-IB2W1Fzj.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
3
3
  import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Option, Queue, Ref, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
@@ -448,6 +448,8 @@ const CAPXUL_OPERATIONS = {
448
448
  emitAnnotationSaved: "activity.emitAnnotationSaved",
449
449
  get: "activity.get",
450
450
  list: "activity.list",
451
+ /** The SDK's live Activity read over the indexer's SSE stream. */
452
+ subscribe: "activity.subscribe",
451
453
  summary: "activity.summary"
452
454
  },
453
455
  addressBook: {
@@ -946,6 +948,17 @@ const AuthFailedProps = Schema.Struct({
946
948
  auth_type: OptionalString,
947
949
  reason: OptionalString
948
950
  });
951
+ const IdentityRefusedProps = Schema.Struct({
952
+ ...TelemetryEnvelopeProps,
953
+ /** The refused identity event tag, for example CreateOrganization. */
954
+ event: OptionalString,
955
+ /** The machine's refusal code: WRONG_STATE, SUPERSEDED or STALE_EPOCH. */
956
+ refusal_code: OptionalString,
957
+ /** The machine slot the request targeted, for example identity:org. */
958
+ slot: OptionalString,
959
+ /** The machine state at refusal, for example authenticated:claiming. */
960
+ state: OptionalString
961
+ });
949
962
  const AuthSignedOutProps = Schema.Struct(TelemetryEnvelopeProps);
950
963
  const ProvisioningSafeCreatedProps = Schema.Struct({
951
964
  ...TelemetryEnvelopeProps,
@@ -1396,6 +1409,10 @@ Schema.Struct({
1396
1409
  name: Schema.Literal("auth_failed"),
1397
1410
  props: AuthFailedProps
1398
1411
  });
1412
+ Schema.Struct({
1413
+ name: Schema.Literal("identity_refused"),
1414
+ props: IdentityRefusedProps
1415
+ });
1399
1416
  Schema.Struct({
1400
1417
  name: Schema.Literal("auth_signed_out"),
1401
1418
  props: AuthSignedOutProps
@@ -2212,6 +2229,10 @@ const BootstrapEnvelope = Schema.Struct({
2212
2229
  siteBaseUrl: Schema.String,
2213
2230
  openfortPublishableKey: Schema.String,
2214
2231
  shieldPublishableKey: Schema.String,
2232
+ /** Which Openfort third-party auth provider the signer presents; absent means better-auth. */
2233
+ openfortAuthProvider: Schema.optional(Schema.Literals(["better-auth", "oidc"])),
2234
+ /** Origin of the Ponder indexer the SDK reads directly; absent means no indexer reads. */
2235
+ indexerUrl: Schema.optional(Schema.String),
2215
2236
  engineeringTelemetry: Schema.optional(EngineeringTelemetryBootstrapPolicy)
2216
2237
  })
2217
2238
  });
@@ -2286,6 +2307,7 @@ const CAPXUL_FUNCTIONS = {
2286
2307
  prepareOrganizationPaymentExecution: "moneyExecution/paymentCommandActions:prepareOrganizationPaymentExecution",
2287
2308
  submitPaymentCommandExecution: "moneyExecution/paymentCommandActions:submitPaymentCommandExecution"
2288
2309
  },
2310
+ "moneyExecution/providerSendWitness": { read: "moneyExecution/providerSendWitness:read" },
2289
2311
  media: {
2290
2312
  generateUploadUrl: "media:generateUploadUrl",
2291
2313
  setOrgLogo: "media:setOrgLogo",
@@ -2334,8 +2356,7 @@ const CAPXUL_FUNCTIONS = {
2334
2356
  listMine: "org/queries:listMine",
2335
2357
  listRolesByOrgId: "org/queries:listRolesByOrgId",
2336
2358
  loadByOrgId: "org/queries:loadByOrgId",
2337
- me: "org/queries:me",
2338
- pendingSetup: "org/queries:pendingSetup"
2359
+ me: "org/queries:me"
2339
2360
  },
2340
2361
  "smartAccount/actions": {
2341
2362
  claim: "smartAccount/actions:claim",
@@ -2346,6 +2367,11 @@ const CAPXUL_FUNCTIONS = {
2346
2367
  loadByAuthUserId: "smartAccount/queries:loadByAuthUserId",
2347
2368
  loadBySmartAccountAddress: "smartAccount/queries:loadBySmartAccountAddress"
2348
2369
  },
2370
+ e2eControls: {
2371
+ holdUserOperationReceipt: "e2eControls:holdUserOperationReceipt",
2372
+ isUserOperationReceiptHeld: "e2eControls:isUserOperationReceiptHeld",
2373
+ releaseUserOperationReceipt: "e2eControls:releaseUserOperationReceipt"
2374
+ },
2349
2375
  system: { health: "system:health" }
2350
2376
  };
2351
2377
  //#endregion
@@ -5657,7 +5683,7 @@ const moneyExecutionContract = {
5657
5683
  };
5658
5684
  //#endregion
5659
5685
  //#region package.json
5660
- var version = "4.2.0-rc.7";
5686
+ var version = "4.2.0-rc.9";
5661
5687
  //#endregion
5662
5688
  //#region src/telemetry/exception-projection.ts
5663
5689
  /** Fixed fallback for failures that have no safe message. */
@@ -5850,6 +5876,17 @@ function observeFailedResult(result, adapter, operation, origin) {
5850
5876
  return result;
5851
5877
  }
5852
5878
  /**
5879
+ * @internal Reports a failure that can never become a method result.
5880
+ *
5881
+ * A reactive stream is torn down after its call already returned, so nothing
5882
+ * downstream observes it. `captureException` is the right kind: the caller did
5883
+ * not ask for the end, so it is not an expected product outcome.
5884
+ */
5885
+ function observeStreamFailure(adapter, operation, cause) {
5886
+ if (adapter === void 0) return;
5887
+ report(adapter, "exception", operation, cause, resolveAdapterSnapshot(adapter));
5888
+ }
5889
+ /**
5853
5890
  * One failure, one event. The identity machine reports a failed transition
5854
5891
  * first; when the same `CapxulError` then surfaces as a public method result
5855
5892
  * (directly or down its `cause` chain), the boundary drops that second report.
@@ -8673,7 +8710,7 @@ function isTransientBootstrapFailure(error) {
8673
8710
  const status = error.details?.httpStatus;
8674
8711
  return typeof status === "number" && isTransientHttpStatus(status);
8675
8712
  }
8676
- async function safeText(res) {
8713
+ async function safeText$1(res) {
8677
8714
  try {
8678
8715
  return await res.text();
8679
8716
  } catch {
@@ -8746,6 +8783,8 @@ var HttpBootstrapAdapter = class {
8746
8783
  siteBaseUrl: normalizeRuntimeUrl("siteBaseUrl", decodedState.siteBaseUrl),
8747
8784
  openfortPublishableKey: decodedState.openfortPublishableKey,
8748
8785
  shieldPublishableKey: decodedState.shieldPublishableKey,
8786
+ ...decodedState.openfortAuthProvider === void 0 ? {} : { openfortAuthProvider: decodedState.openfortAuthProvider },
8787
+ ...decodedState.indexerUrl === void 0 ? {} : { indexerUrl: decodedState.indexerUrl },
8749
8788
  ...decodedEngineeringTelemetry !== void 0 && Result.isSuccess(decodedEngineeringTelemetry) ? { engineeringTelemetry: decodedEngineeringTelemetry.success } : {}
8750
8789
  };
8751
8790
  },
@@ -8754,7 +8793,7 @@ var HttpBootstrapAdapter = class {
8754
8793
  return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
8755
8794
  }
8756
8795
  });
8757
- return Effect.promise(() => safeText(res)).pipe(Effect.flatMap((body) => {
8796
+ return Effect.promise(() => safeText$1(res)).pipe(Effect.flatMap((body) => {
8758
8797
  if (res.status === 401 || body.startsWith("NOT_AUTHENTICATED")) return Effect.fail(bootstrapErrorFromCapxul("notAuthenticated", Errors.notAuthenticated()));
8759
8798
  if (res.status === 400 || body.startsWith("INVALID_INPUT")) return Effect.fail(bootstrapErrorFromCapxul("invalidInput", Errors.invalidInput("publishableKey", "rejected by bootstrap")));
8760
8799
  const responseBody = body.slice(0, 300);
@@ -8984,6 +9023,12 @@ function makeConnectionId() {
8984
9023
  }
8985
9024
  //#endregion
8986
9025
  //#region src/adapters/convex-call/ConvexCallAdapter.ts
9026
+ /**
9027
+ * A socket that has not emitted `close` within this window is not going to.
9028
+ * ponytail: one fixed bound; `closeTimeoutMs` is the knob if a slow runtime
9029
+ * ever needs a different one.
9030
+ */
9031
+ const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
8987
9032
  /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
8988
9033
  const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
8989
9034
  "payroll/actions:authorizeRun",
@@ -9052,7 +9097,9 @@ var ConvexCallAdapter = class {
9052
9097
  #applicationId;
9053
9098
  #observation;
9054
9099
  #connectionMonitor;
9100
+ #closeTimeoutMs;
9055
9101
  constructor(deps) {
9102
+ this.#closeTimeoutMs = deps.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
9056
9103
  this.#connectionMonitor = new ConvexConnectionMonitor();
9057
9104
  const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
9058
9105
  this.#client = deps.client ?? new ConvexClient(deps.convexUrl, webSocketConstructor === void 0 ? {} : { webSocketConstructor });
@@ -9151,11 +9198,33 @@ var ConvexCallAdapter = class {
9151
9198
  callback({ status: "loading" });
9152
9199
  })));
9153
9200
  }
9201
+ /**
9202
+ * Teardown always completes. `ConvexClient.close()` resolves only when the
9203
+ * socket emits `close`, and `WebSocketManager.close()` in the `connecting`
9204
+ * state arms `ws.onopen = () => ws.close()` without ever closing the socket
9205
+ * itself — so a handshake that stalls right after a server-initiated close
9206
+ * (code 1012, which is what a Convex deploy sends) leaves that promise
9207
+ * pending for as long as the page lives. Convex marks the client closed and
9208
+ * terminates the socket manager synchronously before returning it, so
9209
+ * abandoning it drops nothing. A rejection is dropped for the same reason —
9210
+ * the layer release turns it into a defect that fails the whole scope close,
9211
+ * and teardown has no caller who can act on it.
9212
+ */
9154
9213
  async close() {
9155
9214
  this.#connectionMonitor.close();
9156
- await this.#client.close();
9215
+ await settledWithin(this.#client.close(), this.#closeTimeoutMs);
9157
9216
  }
9158
9217
  };
9218
+ function settledWithin(promise, timeoutMs) {
9219
+ return new Promise((resolve) => {
9220
+ const timer = setTimeout(resolve, timeoutMs);
9221
+ const finish = () => {
9222
+ clearTimeout(timer);
9223
+ resolve();
9224
+ };
9225
+ promise.then(finish, finish);
9226
+ });
9227
+ }
9159
9228
  function ConvexCallLayer(deps) {
9160
9229
  return Layer.effect(ConvexCallPortTag, Effect.acquireRelease(Effect.sync(() => new ConvexCallAdapter(deps)), (adapter) => Effect.promise(() => adapter.close()).pipe(Effect.orDie)));
9161
9230
  }
@@ -10265,8 +10334,9 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10265
10334
  });
10266
10335
  throw failOpenfort("configure", "no-secure-context", /* @__PURE__ */ new Error("Web Crypto unavailable: browser is not a secure context"));
10267
10336
  }
10268
- function betterAuthSessionUrl() {
10269
- return `${authBaseUrl}/get-session`;
10337
+ const authProvider = bootstrap.openfortAuthProvider ?? "better-auth";
10338
+ function accessTokenUrl() {
10339
+ return authProvider === "oidc" ? `${authBaseUrl}/convex/token` : `${authBaseUrl}/get-session`;
10270
10340
  }
10271
10341
  function encryptionSessionUrl() {
10272
10342
  return `${authBaseUrl}/encryption-session`;
@@ -10289,7 +10359,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10289
10359
  tokenCalls += 1;
10290
10360
  const tokenStartedAt = Date.now();
10291
10361
  try {
10292
- const response = await fetch(betterAuthSessionUrl(), {
10362
+ const response = await fetch(accessTokenUrl(), {
10293
10363
  credentials: "include",
10294
10364
  cache: "no-store"
10295
10365
  });
@@ -10302,7 +10372,8 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10302
10372
  });
10303
10373
  return null;
10304
10374
  }
10305
- const token = (await response.json()).session?.token?.trim();
10375
+ const body = await response.json();
10376
+ const token = (authProvider === "oidc" ? body.token : body.session?.token)?.trim();
10306
10377
  if (token === void 0 || token.length === 0) {
10307
10378
  diagnostic?.trace("openfort.token", {
10308
10379
  ok: false,
@@ -10314,6 +10385,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10314
10385
  diagnostic?.trace("openfort.token", {
10315
10386
  ok: true,
10316
10387
  tokenPresent: true,
10388
+ provider: authProvider,
10317
10389
  call: tokenCalls,
10318
10390
  duration_ms: Date.now() - tokenStartedAt,
10319
10391
  elapsed_ms: sinceCycleStart()
@@ -10332,7 +10404,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
10332
10404
  baseConfiguration: { publishableKey: bootstrap.openfortPublishableKey },
10333
10405
  shieldConfiguration: { shieldPublishableKey: bootstrap.shieldPublishableKey },
10334
10406
  thirdPartyAuth: {
10335
- provider: ThirdPartyOAuthProvider.BETTER_AUTH,
10407
+ provider: authProvider === "oidc" ? ThirdPartyOAuthProvider.OIDC : ThirdPartyOAuthProvider.BETTER_AUTH,
10336
10408
  getAccessToken: fetchBetterAuthAccessToken
10337
10409
  }
10338
10410
  });
@@ -13333,11 +13405,28 @@ const authFailure = (record, reason, mode) => ({
13333
13405
  ...linkage(record)
13334
13406
  }
13335
13407
  });
13408
+ const REFUSAL_EVENTS = /* @__PURE__ */ new Set([
13409
+ "ClaimAccount",
13410
+ "RetryAccount",
13411
+ "CreateOrganization",
13412
+ "AttachOrganization",
13413
+ "RetryOrganization"
13414
+ ]);
13336
13415
  function mapIdentityProductObservation(record, state) {
13337
13416
  if (record.machine !== "identity" || !IDENTITY_EVENT_TAG_SET.has(record.event)) return NONE;
13338
13417
  if (record.outcome === "cancelled") return NONE;
13339
13418
  if (record.slot === "identity:auth" && record.outcome === "refused") return authFailure(record, record.refusal_code, void 0);
13340
13419
  if (record.slot === "identity:auth" && record.outcome === "failed") return authFailure(record, record.error_code, record.failure?.mode);
13420
+ if (record.outcome === "refused" && REFUSAL_EVENTS.has(record.event)) return {
13421
+ name: "identity_refused",
13422
+ props: {
13423
+ event: record.event,
13424
+ refusal_code: record.refusal_code,
13425
+ slot: record.slot,
13426
+ state: record.state,
13427
+ ...linkage(record)
13428
+ }
13429
+ };
13341
13430
  if (record.outcome !== "applied") return NONE;
13342
13431
  switch (APPLIED_ACTION_BY_EVENT[record.event]) {
13343
13432
  case "none": return NONE;
@@ -13663,6 +13752,7 @@ function assembleCapxulClient(input) {
13663
13752
  identity: identityRuntime,
13664
13753
  bootstrap: input.bootstrap,
13665
13754
  accounts: { fund: accounts.fund },
13755
+ ...input.ports.indexerRead === void 0 ? {} : { indexer: input.ports.indexerRead },
13666
13756
  organizationSetup: { getProofReceipt: (orgId) => runPortEffect(input.ports.convexCall.query(organizationSetupProofReceiptQuery, { orgId })) },
13667
13757
  telemetry: input.ports.telemetry,
13668
13758
  captureExternalAddressAcknowledged: (address, organizationId) => {
@@ -13864,6 +13954,382 @@ function snapshotHostObservability(observability) {
13864
13954
  function isPromiseLike(value) {
13865
13955
  return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value;
13866
13956
  }
13957
+ //#endregion
13958
+ //#region src/ports/indexer-read.ts
13959
+ /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
13960
+ const INDEXER_MAX_LIMIT = 1e3;
13961
+ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
13962
+ /**
13963
+ * Refuse a page the indexer would refuse, before any implementation spends a
13964
+ * round trip on it. Every implementation calls this, so a consumer test written
13965
+ * against the hermetic adapter cannot pass on input production rejects. Throws
13966
+ * a `CapxulError`; callers convert it to an `IndexerReadError`.
13967
+ */
13968
+ function assertIndexerPage(input) {
13969
+ const safe = String(input.safe).toLowerCase();
13970
+ if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
13971
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 1e3) throw Errors.invalidInput("limit", `must be an integer from 1 to ${INDEXER_MAX_LIMIT}`);
13972
+ const offset = input.offset ?? 0;
13973
+ if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
13974
+ return safe;
13975
+ }
13976
+ var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
13977
+ function indexerReadErrorFromCapxul(operation, error, cause = error) {
13978
+ return new IndexerReadError({
13979
+ operation,
13980
+ publicCode: error.code,
13981
+ publicError: error,
13982
+ cause,
13983
+ ...error.details === void 0 ? {} : { details: error.details }
13984
+ });
13985
+ }
13986
+ Context.Service()("@capxul/sdk/ports/IndexerReadPort");
13987
+ //#endregion
13988
+ //#region src/adapters/ponder/decode.ts
13989
+ /**
13990
+ * Decode the indexer's rows at the trust boundary.
13991
+ *
13992
+ * Ponder returns the raw PostgreSQL result: `integer` arrives as a number,
13993
+ * `numeric(78)` and `text` arrive as strings. Amounts, block numbers and
13994
+ * timestamps are `numeric(78)`, so they become `bigint` here and never pass
13995
+ * through `Number`. Every decoder throws a `CapxulError`; the adapter turns
13996
+ * that into a typed `IndexerReadError`.
13997
+ */
13998
+ const HEX = /^0x[0-9a-fA-F]+$/;
13999
+ function field(row, key) {
14000
+ if (!(key in row)) throw Errors.invalidInput(key, "missing from the indexer row");
14001
+ return row[key];
14002
+ }
14003
+ function text(row, key) {
14004
+ const value = field(row, key);
14005
+ if (typeof value !== "string") throw Errors.invalidInput(key, "must be a string");
14006
+ return value;
14007
+ }
14008
+ function hex(row, key) {
14009
+ const value = text(row, key);
14010
+ if (!HEX.test(value)) throw Errors.invalidInput(key, "must be a hex string");
14011
+ return value.toLowerCase();
14012
+ }
14013
+ function integer(row, key) {
14014
+ const value = field(row, key);
14015
+ const parsed = typeof value === "string" ? Number(value) : value;
14016
+ if (typeof parsed !== "number" || !Number.isSafeInteger(parsed)) throw Errors.invalidInput(key, "must be an integer");
14017
+ return parsed;
14018
+ }
14019
+ /** `numeric(78)` crosses the wire as a decimal string; keep every digit. */
14020
+ function big(row, key) {
14021
+ const value = field(row, key);
14022
+ if (typeof value === "bigint") return value;
14023
+ const raw = typeof value === "number" ? String(value) : value;
14024
+ if (typeof raw !== "string" || !/^-?[0-9]+$/.test(raw)) throw Errors.invalidInput(key, "must be an exact integer amount");
14025
+ return BigInt(raw);
14026
+ }
14027
+ function asRow(value) {
14028
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw Errors.invalidInput("row", "must be an object");
14029
+ return value;
14030
+ }
14031
+ function identity(row) {
14032
+ return {
14033
+ id: text(row, "id"),
14034
+ chainId: toChainId(integer(row, "chain_id")),
14035
+ txHash: toTxHash(hex(row, "tx_hash")),
14036
+ transactionLogOrdinal: integer(row, "transaction_log_ordinal"),
14037
+ blockNumber: big(row, "block_number"),
14038
+ blockHash: hex(row, "block_hash"),
14039
+ timestamp: big(row, "timestamp")
14040
+ };
14041
+ }
14042
+ function decodeTransfer(value) {
14043
+ const row = asRow(value);
14044
+ return {
14045
+ ...identity(row),
14046
+ assetId: toAssetId(text(row, "asset_id")),
14047
+ token: toAddress(hex(row, "token")),
14048
+ from: toAddress(hex(row, "from")),
14049
+ to: toAddress(hex(row, "to")),
14050
+ amount: big(row, "amount")
14051
+ };
14052
+ }
14053
+ function decodePayment(value) {
14054
+ const row = asRow(value);
14055
+ return {
14056
+ ...identity(row),
14057
+ contract: toAddress(hex(row, "contract")),
14058
+ settlementId: hex(row, "settlement_id"),
14059
+ sender: toAddress(hex(row, "sender")),
14060
+ recipient: toAddress(hex(row, "recipient")),
14061
+ assetId: toAssetId(text(row, "asset_id")),
14062
+ token: toAddress(hex(row, "token")),
14063
+ amount: big(row, "amount"),
14064
+ kind: integer(row, "kind"),
14065
+ documentHash: hex(row, "document_hash")
14066
+ };
14067
+ }
14068
+ /** The `balance` view exposes the signed sum under the underlying column name `to`. */
14069
+ function decodeBalance(value) {
14070
+ const row = asRow(value);
14071
+ return {
14072
+ chainId: toChainId(integer(row, "chain_id")),
14073
+ assetId: toAssetId(text(row, "asset_id")),
14074
+ address: toAddress(hex(row, "to")),
14075
+ amount: big(row, "amount")
14076
+ };
14077
+ }
14078
+ const DECODERS = {
14079
+ transfer: decodeTransfer,
14080
+ payment: decodePayment,
14081
+ balance: decodeBalance
14082
+ };
14083
+ /** Decode one `/sql/db` or `/sql/live` payload. Ponder returns `{ rows: [...] }`. */
14084
+ function decodeRows(table, body) {
14085
+ const rows = asRow(body).rows;
14086
+ if (!Array.isArray(rows)) throw Errors.invalidInput("rows", "must be an array");
14087
+ const decode = DECODERS[table];
14088
+ return rows.map(decode);
14089
+ }
14090
+ /** Decode Ponder's native `/status`: `{ [chainName]: { id, block: { number, timestamp } } }`. */
14091
+ function decodeStatus(body) {
14092
+ const chains = asRow(body);
14093
+ return Object.entries(chains).map(([chain, value]) => {
14094
+ const entry = asRow(value);
14095
+ const block = asRow(field(entry, "block"));
14096
+ return {
14097
+ chain,
14098
+ chainId: toChainId(integer(entry, "id")),
14099
+ blockNumber: big(block, "number"),
14100
+ blockTimestamp: big(block, "timestamp")
14101
+ };
14102
+ });
14103
+ }
14104
+ //#endregion
14105
+ //#region src/adapters/ponder/statements.ts
14106
+ /**
14107
+ * The exact statements the indexer accepts.
14108
+ *
14109
+ * `apps/indexer/src/queries.ts` compiles its allowlist with `@ponder/client`
14110
+ * and compares `sql`, `params` and `typings` byte for byte against the caller's
14111
+ * statement. Anything else gets 403. The SDK therefore reproduces the compiled
14112
+ * text here rather than pulling Ponder and Drizzle into a published browser
14113
+ * bundle to regenerate three fixed statements.
14114
+ *
14115
+ * `statements.test.ts` pins the text. When the indexer schema or a query in
14116
+ * that allowlist changes, this file changes with it or every read fails 403.
14117
+ */
14118
+ const HEADS = {
14119
+ transfer: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"block_number\", \"block_hash\", \"timestamp\", \"asset_id\", \"token\", \"from\", \"to\", \"amount\" from \"transfer\" where (\"transfer\".\"from\" = $1 or \"transfer\".\"to\" = $2) order by \"transfer\".\"block_number\" desc, \"transfer\".\"id\" desc",
14120
+ payment: "select \"id\", \"chain_id\", \"tx_hash\", \"transaction_log_ordinal\", \"block_number\", \"block_hash\", \"timestamp\", \"contract\", \"settlement_id\", \"sender\", \"recipient\", \"asset_id\", \"token\", \"amount\", \"kind\", \"document_hash\" from \"payment\" where (\"payment\".\"sender\" = $1 or \"payment\".\"recipient\" = $2) order by \"payment\".\"block_number\" desc, \"payment\".\"id\" desc",
14121
+ balance: "select \"chain_id\", \"asset_id\", \"to\", \"amount\" from \"balance\" where \"balance\".\"to\" = $1"
14122
+ };
14123
+ /** How many times the statement binds the Safe address. */
14124
+ const ACTOR_PARAMS = {
14125
+ transfer: 2,
14126
+ payment: 2,
14127
+ balance: 1
14128
+ };
14129
+ /**
14130
+ * Build the compiled query for one table and page. Throws a `CapxulError` for
14131
+ * a page or address the indexer would refuse, so the caller never spends a
14132
+ * round trip to learn it built the request wrong.
14133
+ */
14134
+ function ponderQuery(table, input) {
14135
+ const safe = assertIndexerPage(input);
14136
+ const offset = input.offset ?? 0;
14137
+ const actors = ACTOR_PARAMS[table];
14138
+ const params = [];
14139
+ for (let i = 0; i < actors; i++) params.push(safe);
14140
+ params.push(input.limit);
14141
+ let sql = `${HEADS[table]} limit $${actors + 1}`;
14142
+ if (offset > 0) {
14143
+ params.push(offset);
14144
+ sql = `${sql} offset $${actors + 2}`;
14145
+ }
14146
+ return {
14147
+ sql,
14148
+ params,
14149
+ typings: params.map(() => "none")
14150
+ };
14151
+ }
14152
+ /**
14153
+ * The `?sql=` value. `@ponder/client` sends `superjson.stringify(query)`; for a
14154
+ * payload of strings and numbers superjson emits no `meta`, so the wire form is
14155
+ * exactly this envelope.
14156
+ */
14157
+ function ponderQueryParam(query) {
14158
+ return JSON.stringify({ json: query });
14159
+ }
14160
+ //#endregion
14161
+ //#region src/adapters/ponder/sse.ts
14162
+ /**
14163
+ * The smallest SSE reader that can carry an `Authorization` header.
14164
+ *
14165
+ * `@ponder/client` opens its live query with `EventSource`, which accepts no
14166
+ * headers, so it cannot reach the guarded indexer at all. The indexer's CORS
14167
+ * policy allows exactly one request header (`Authorization`), so this reader
14168
+ * sends nothing else. Ponder writes one `data:` line per frame.
14169
+ */
14170
+ async function readEventStream(body, onData, stopped) {
14171
+ const reader = body.getReader();
14172
+ const decoder = new TextDecoder();
14173
+ let buffer = "";
14174
+ try {
14175
+ for (;;) {
14176
+ const { done, value } = await reader.read();
14177
+ if (done || stopped()) return;
14178
+ buffer += decoder.decode(value, { stream: true });
14179
+ let split = buffer.indexOf("\n\n");
14180
+ while (split !== -1) {
14181
+ const frame = buffer.slice(0, split);
14182
+ buffer = buffer.slice(split + 2);
14183
+ const data = frameData(frame);
14184
+ if (data !== void 0) onData(data);
14185
+ if (stopped()) return;
14186
+ split = buffer.indexOf("\n\n");
14187
+ }
14188
+ }
14189
+ } finally {
14190
+ await reader.cancel().catch(() => void 0);
14191
+ }
14192
+ }
14193
+ function frameData(frame) {
14194
+ const lines = [];
14195
+ for (const line of frame.split("\n")) {
14196
+ if (!line.startsWith("data:")) continue;
14197
+ lines.push(line.slice(line.startsWith("data: ") ? 6 : 5));
14198
+ }
14199
+ return lines.length === 0 ? void 0 : lines.join("\n");
14200
+ }
14201
+ //#endregion
14202
+ //#region src/adapters/ponder/PonderIndexerAdapter.ts
14203
+ var PonderIndexerAdapter = class {
14204
+ #baseUrl;
14205
+ #token;
14206
+ #fetch;
14207
+ #observation;
14208
+ constructor(deps) {
14209
+ this.#baseUrl = deps.baseUrl.replace(/\/$/, "");
14210
+ this.#token = deps.token;
14211
+ this.#observation = deps.observation;
14212
+ this.#fetch = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
14213
+ }
14214
+ read(table, input) {
14215
+ const operation = `indexer.read.${table}`;
14216
+ return Effect.tryPromise({
14217
+ try: async () => {
14218
+ return decodeRows(table, await (await this.#request("db", table, input, operation)).json());
14219
+ },
14220
+ catch: (cause) => toIndexerReadError(operation, cause)
14221
+ });
14222
+ }
14223
+ subscribe(table, input, callback) {
14224
+ const operation = CAPXUL_OPERATIONS.activity.subscribe;
14225
+ return Effect.sync(() => {
14226
+ let stopped = false;
14227
+ const unsubscribe = () => {
14228
+ stopped = true;
14229
+ };
14230
+ callback({ status: "loading" });
14231
+ this.#stream(table, input, operation, callback, () => stopped).catch(() => void 0);
14232
+ return unsubscribe;
14233
+ });
14234
+ }
14235
+ status = Effect.tryPromise({
14236
+ try: async () => {
14237
+ const response = await this.#fetch(`${this.#baseUrl}/status`, { method: "GET" });
14238
+ if (!response.ok) throw httpError("indexer.status", response.status, await safeText(response));
14239
+ return decodeStatus(await response.json());
14240
+ },
14241
+ catch: (cause) => toIndexerReadError("indexer.status", cause)
14242
+ });
14243
+ /** One authenticated `/sql/:method` call. Throws a `CapxulError` on refusal. */
14244
+ async #request(method, table, input, operation) {
14245
+ const query = ponderQuery(table, input);
14246
+ const token = await this.#token({ forceRefreshToken: false });
14247
+ if (token === null || token === "") throw Errors.notAuthenticated();
14248
+ const url = `${this.#baseUrl}/sql/${method}?sql=${encodeURIComponent(ponderQueryParam(query))}`;
14249
+ const headers = { authorization: `Bearer ${token}` };
14250
+ if (method === "live") headers.accept = "text/event-stream";
14251
+ let response;
14252
+ try {
14253
+ response = await this.#fetch(url, {
14254
+ method: "GET",
14255
+ headers
14256
+ });
14257
+ } catch (cause) {
14258
+ throw Errors.networkError(operation, cause, {
14259
+ provider: "indexer",
14260
+ failure_mode: "upstream-down"
14261
+ });
14262
+ }
14263
+ if (!response.ok) throw httpError(operation, response.status, await safeText(response));
14264
+ return response;
14265
+ }
14266
+ /**
14267
+ * Follow the live stream until the caller unsubscribes or the indexer closes
14268
+ * it. The indexer closes the stream on token expiry or revoked authority
14269
+ * with no frame, so any end the caller did not ask for is a teardown: it
14270
+ * reaches `captureException` under `activity.subscribe` and the caller sees
14271
+ * one `error` snapshot.
14272
+ */
14273
+ async #stream(table, input, operation, callback, stopped) {
14274
+ let failure;
14275
+ try {
14276
+ const response = await this.#request("live", table, input, operation);
14277
+ if (response.body === null) throw Errors.providerError("indexer", operation, "empty stream");
14278
+ await readEventStream(response.body, (data) => {
14279
+ try {
14280
+ callback({
14281
+ status: "ok",
14282
+ value: decodeRows(table, JSON.parse(data))
14283
+ });
14284
+ } catch (cause) {
14285
+ failure = toPublicError$1(operation, cause);
14286
+ }
14287
+ }, () => stopped() || failure !== void 0);
14288
+ } catch (cause) {
14289
+ failure = toPublicError$1(operation, cause);
14290
+ }
14291
+ if (stopped()) return;
14292
+ const error = failure ?? Errors.capabilityUnavailable("indexer", operation);
14293
+ observeStreamFailure(this.#observation, CAPXUL_OPERATIONS.activity.subscribe, error);
14294
+ callback({
14295
+ status: "error",
14296
+ error
14297
+ });
14298
+ }
14299
+ };
14300
+ /**
14301
+ * Map an indexer refusal into the shared error vocabulary.
14302
+ *
14303
+ * 401 is the expected outcome of an expired or revoked session. 403 is not:
14304
+ * the indexer accepts only its own compiled statements for a Safe in the
14305
+ * claim, so a refusal means this SDK and that deployment disagree, or the
14306
+ * session gained a Safe and still holds the older token. Both must be loud.
14307
+ */
14308
+ function httpError(operation, status, body) {
14309
+ if (status === 401) return Errors.notAuthenticated();
14310
+ if (status === 429) return Errors.rateLimited({ resource: "indexer" });
14311
+ if (status === 503) return Errors.capabilityUnavailable("indexer", operation);
14312
+ return Errors.providerError("indexer", operation, body, {
14313
+ httpStatus: status,
14314
+ ...status >= 500 ? { failure_mode: "upstream-down" } : {},
14315
+ details: { reason: status === 403 ? "scope-refused" : "http-status" }
14316
+ });
14317
+ }
14318
+ function toPublicError$1(operation, cause) {
14319
+ if (cause instanceof CapxulError) return cause;
14320
+ return Errors.providerError("indexer", operation, cause);
14321
+ }
14322
+ function toIndexerReadError(operation, cause) {
14323
+ if (cause instanceof IndexerReadError) return cause;
14324
+ return indexerReadErrorFromCapxul(operation, toPublicError$1(operation, cause), cause);
14325
+ }
14326
+ async function safeText(response) {
14327
+ try {
14328
+ return await response.text();
14329
+ } catch {
14330
+ return "";
14331
+ }
14332
+ }
13867
14333
  const SDK_VERSION = version;
13868
14334
  /**
13869
14335
  * Project the built graph into the flat `FlowPorts` record the method bundles
@@ -14113,7 +14579,16 @@ async function createProductionAdapters(input) {
14113
14579
  }));
14114
14580
  const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime));
14115
14581
  const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
14116
- const ports = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
14582
+ const collected = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
14583
+ const indexerUrl = input.indexerUrl ?? bootstrapResult.value.indexerUrl;
14584
+ const indexerRead = productionIndexerReadPort(indexerUrl === void 0 ? input : {
14585
+ ...input,
14586
+ indexerUrl
14587
+ }, collected, observation);
14588
+ const ports = indexerRead === void 0 ? collected : {
14589
+ ...collected,
14590
+ indexerRead
14591
+ };
14117
14592
  const stopConnectionObservation = observeProductionConvexConnection(ports.convexCall, context);
14118
14593
  const close = idempotentClose(async () => {
14119
14594
  try {
@@ -14445,5 +14920,27 @@ function idempotentClose(close) {
14445
14920
  await close();
14446
14921
  };
14447
14922
  }
14923
+ /**
14924
+ * Build the Ponder read port for this client, or `undefined` when no indexer
14925
+ * origin was supplied. The token provider is the same
14926
+ * `AuthClientPort.getConvexJwt` closure `ConvexCallPort` uses, so the indexer
14927
+ * and Convex see one session with one scope.
14928
+ */
14929
+ function productionIndexerReadPort(input, ports, observation) {
14930
+ if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
14931
+ return new PonderIndexerAdapter({
14932
+ baseUrl: normalizeHttpUrl("indexerUrl", input.indexerUrl),
14933
+ token: async ({ forceRefreshToken }) => {
14934
+ const token = await Effect.runPromise(Effect.result(ports.authClient.getConvexJwt({
14935
+ forceRefresh: forceRefreshToken,
14936
+ ...input.signal === void 0 ? {} : { signal: input.signal }
14937
+ })));
14938
+ if (Result.isFailure(token)) return null;
14939
+ return String(token.success.token);
14940
+ },
14941
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
14942
+ ...observation === void 0 ? {} : { observation }
14943
+ });
14944
+ }
14448
14945
  //#endregion
14449
14946
  export { signerFailure as A, isRestoring as B, normalizeExceptionErrorKind as C, fromWei as D, safeExceptionLabel as E, CAPXUL_OPERATIONS as F, isCapxulOperation as I, normalizeCapxulOperation as L, PAYMENT_DIRECTIONS as M, PAYMENT_STATUSES as N, isSettingUpLifecycle as O, redactTelemetryEvent as P, destination as R, SDK_VERSION$1 as S, projectSdkException as T, fingerprintPaymentIntent as _, smartAccountErrorFromCapxul as a, failureDetail as b, identityErrorFromCapxul as c, embeddedSigner as d, openfortEmbeddedSigner as f, devPrivateKeySigner as g, deriveDevPrivateKey as h, assembleCapxulClient as i, resolveFailureMode as j, injectedWalletSigner as k, convexCallErrorFromCapxul as l, openfortEmbeddedWalletPort as m, createCapxulClientWithSignerControls as n, accountReadErrorFromCapxul as o, openfortEmbeddedSignerFromWallet as p, postHogObservability as r, wireChainId as s, createCapxulClient as t, bootstrapErrorFromCapxul as u, toWei as v, normalizeExceptionOperation as w, EXCEPTION_MESSAGE as x, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT as y, isClaimed as z };