@capxul/sdk 4.20.0-beta.1 → 4.20.0-beta.2

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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { $ as PayrollGroupId, B as ChainId, Ct as ChainCause, D as Account, Dt as RevertSummary, E as CAPXUL_PAYMENTS_V2_ADDRESS, Et as FailureMode, F as AssetId, G as EVM_ADDRESS_RE, H as CurrencyCode, I as AuthSession, J as Handle, L as AuthUserId, O as AccountId, P as AssetAmount, St as CHAIN_UPSTREAMS, Tt as FAILURE_MODES, V as CountryCode, X as OrgId, Y as Money, Z as PartyId, _ as Session, _t as CapxulErrorCode, a as Eip1193RequestProvider, at as RoleKey, c as injectedWalletSigner, ct as TesterKind, d as AccountRequirement, dt as toAddress, et as PayrollRunId, f as Eip1193Provider, ft as toCountryCode, g as Profile, gt as CapxulError, h as CapxulResult, ht as CAPXUL_ERROR_CODES, i as CapxulSigner, k as Address, l as AccountProvider, m as localPrivateKeyAccountProvider, mt as toPartyId, o as SignerStatus, p as eip1193AccountProvider, pt as toHandle, r as CapxulDigestSigner, s as SignerStatusStore, u as AccountProviderSource, v as SmartAccount, vt as CapxulErrorDetails, wt as ChainUpstream, xt as isCapxulError, yt as Errors, z as BudgetId } from "./OAuthBearerAuthClient-C-ip-z8M.mjs";
2
- import { $ as OrganizationPaymentBatchInput, $n as RequestStatus, $t as Destination, A as OrgScopedMethods, An as Ref, At as AddressBookAddInput, B as AuthorizeRunOptions, Bn as SmartAccountMethods, Bt as ActivityFilter, C as CurrentUserMethods, Cn as PaymentMoney, Ct as AccountMethods, D as MemberStatus, Dn as PaymentsMethods, Dt as ActorRequest, E as InviteMemberInput, En as PaymentType, Et as ActorRelationshipMethods, F as ResendInviteTokenInput, Fn as AccountLifecycle, Ft as InboxItem, G as PayrollGroupsMethods, Gn as ActorRef, Gt as ActivityPage, H as PayrollGroup, Hn as OrgLifecycle, Ht as ActivityKind, I as RoleDefinition, In as AccountSetupStep, It as InboxMethods, J as PayrollRun, Jn as SubmittedPermissionExecution, Jt as ActivitySummary, K as PayrollMethods, Kn as CurrentHoldings, Kt as ActivityRange, L as RoleSpendCap, Ln as isSettingUpLifecycle, Lt as ActivityAnnotation, M as OrgView, Mn as TargetReference, Mt as AddressBookLabelInput, N as OrganizationAccount, Nn as TargetsMethods, Nt as AddressBookMethods, O as MemberView, On as PaymentsPayInput, Ot as ActorRequestIssueInput, P as OrganizationAuditLogItem, Pn as fingerprintPaymentIntent, Pt as InboxApproveInput, Q as PayrollTermsUnit, Qn as PaymentStatus$1, Qt as DepositInstructions, R as RoleView, Rn as CompleteProfileInput, Rt as ActivityAnnotationInput, S as CurrentUserContext, Sn as PaymentDocumentsMethods, St as ReadyAccountLifecycle, T as DetectPendingOrgInvitationsResult, Tn as PaymentTiming, Tt as ActorProfileMethods, U as PayrollGroupInput, Un as OrgSetupStep, Ut as ActivityListParams, V as PayrollEngagementTerms, Vn as AuthMethods, Vt as ActivityItem, W as PayrollGroupMember, Wn as PayoutAddress, Wt as ActivityMethods, X as PayrollRunStatus, Xn as PAYMENT_DIRECTIONS, Xt as ActivitySummaryTotal, Y as PayrollRunItemInput, Yn as InboxStatus, Yt as ActivitySummaryParams, Z as PayrollRuns, Zn as PAYMENT_STATUSES, Zt as ActorReference, _ as HoldingsMethods, _n as PaymentDirection, _t as OnboardingMethods, a as ObservationContext, an as DestinationRemoveInput, ar as IdentityEvent, at as PermissionCreateInput, b as SystemHealth, bn as PaymentDocumentRender, bt as PersonOnboarding, c as HostObservability, cn as MeMethods, cr as Readiness, ct as PermissionReplaceInput, d as postHogObservability, dn as OfframpMethods, dr as isClaimed, dt as Budget, en as DestinationAddInput, et as OrganizationPaymentInput, f as CapxulClient, fn as OfframpQuote, fr as isRestoring, ft as OrgMe, g as IdentityRuntimeSendResult, gn as PaymentActivityEvidence, gt as CompletedPersonProfile, h as IdentityRuntime, hn as Payment, ht as AccountsMethods, i as ObservationAdapter, in as DestinationRail, ir as Destination$1, it as PermissionChangeInput, j as OrgTemplate, jn as ResolvedTarget, jt as AddressBookEntry, k as OrgMethods, kn as RecipientResolution, kt as ActorRequestsMethods, l as PostHogObservabilityClient, ln as MeProfile, lr as StateLabel, lt as PermissionRevokeInput, m as IdentityProfileDetails, mn as OfframpStatus, mr as InvocationControls, mt as OrgMeOptions, nn as DestinationListInput, nt as OrganizationPaymentsMethods, o as ObservationDelivery, on as DestinationsMethods, or as IdentityState, ot as PermissionMethods, p as CreateCapxulClientInput, pn as OfframpQuoteInput, pr as IdentityTransition, pt as OrgMeMethod, q as PayrollOptions, qn as Permission, qt as ActivityReference, r as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, rn as DestinationPayload, rr as TelemetryPort, rt as PermissionAssignInput, s as SdkFailureObservation, sn as FinancialOpsMethods, sr as OrgLane, st as PermissionOptions, t as CapxulClientInput, tn as DestinationKind, tt as OrganizationPaymentItemInput, u as PostHogObservabilityOptions, un as MovementActivityEvidence, ur as destination, ut as PermissionReadResult, v as Holding, vn as PaymentDocumentKind, vt as OrganizationOnboarding, w as CreateOrgInput, wn as PaymentStatus, wt as ActorProfile, x as MediaMethods, xn as PaymentDocumentVerification, xt as PersonOnboardingInput, y as SystemMethods, yn as PaymentDocumentRef, yt as OrganizationOnboardingInput, z as AuthorizeRunInput, zn as IdentityMethods, zt as ActivityDetail } from "./production-DvGDNIwu.mjs";
2
+ import { $ as OrganizationPaymentBatchInput, $n as RequestStatus, $t as Destination, A as OrgScopedMethods, An as Ref, At as AddressBookAddInput, B as AuthorizeRunOptions, Bn as SmartAccountMethods, Bt as ActivityFilter, C as CurrentUserMethods, Cn as PaymentMoney, Ct as AccountMethods, D as MemberStatus, Dn as PaymentsMethods, Dt as ActorRequest, E as InviteMemberInput, En as PaymentType, Et as ActorRelationshipMethods, F as ResendInviteTokenInput, Fn as AccountLifecycle, Ft as InboxItem, G as PayrollGroupsMethods, Gn as ActorRef, Gt as ActivityPage, H as PayrollGroup, Hn as OrgLifecycle, Ht as ActivityKind, I as RoleDefinition, In as AccountSetupStep, It as InboxMethods, J as PayrollRun, Jn as SubmittedPermissionExecution, Jt as ActivitySummary, K as PayrollMethods, Kn as CurrentHoldings, Kt as ActivityRange, L as RoleSpendCap, Ln as isSettingUpLifecycle, Lt as ActivityAnnotation, M as OrgView, Mn as TargetReference, Mt as AddressBookLabelInput, N as OrganizationAccount, Nn as TargetsMethods, Nt as AddressBookMethods, O as MemberView, On as PaymentsPayInput, Ot as ActorRequestIssueInput, P as OrganizationAuditLogItem, Pn as fingerprintPaymentIntent, Pt as InboxApproveInput, Q as PayrollTermsUnit, Qn as PaymentStatus$1, Qt as DepositInstructions, R as RoleView, Rn as CompleteProfileInput, Rt as ActivityAnnotationInput, S as CurrentUserContext, Sn as PaymentDocumentsMethods, St as ReadyAccountLifecycle, T as DetectPendingOrgInvitationsResult, Tn as PaymentTiming, Tt as ActorProfileMethods, U as PayrollGroupInput, Un as OrgSetupStep, Ut as ActivityListParams, V as PayrollEngagementTerms, Vn as AuthMethods, Vt as ActivityItem, W as PayrollGroupMember, Wn as PayoutAddress, Wt as ActivityMethods, X as PayrollRunStatus, Xn as PAYMENT_DIRECTIONS, Xt as ActivitySummaryTotal, Y as PayrollRunItemInput, Yn as InboxStatus, Yt as ActivitySummaryParams, Z as PayrollRuns, Zn as PAYMENT_STATUSES, Zt as ActorReference, _ as HoldingsMethods, _n as PaymentDirection, _t as OnboardingMethods, a as ObservationContext, an as DestinationRemoveInput, ar as IdentityEvent, at as PermissionCreateInput, b as SystemHealth, bn as PaymentDocumentRender, bt as PersonOnboarding, c as HostObservability, cn as MeMethods, cr as Readiness, ct as PermissionReplaceInput, d as postHogObservability, dn as OfframpMethods, dr as isClaimed, dt as Budget, en as DestinationAddInput, et as OrganizationPaymentInput, f as CapxulClient, fn as OfframpQuote, fr as isRestoring, ft as OrgMe, g as IdentityRuntimeSendResult, gn as PaymentActivityEvidence, gt as CompletedPersonProfile, h as IdentityRuntime, hn as Payment, ht as AccountsMethods, i as ObservationAdapter, in as DestinationRail, ir as Destination$1, it as PermissionChangeInput, j as OrgTemplate, jn as ResolvedTarget, jt as AddressBookEntry, k as OrgMethods, kn as RecipientResolution, kt as ActorRequestsMethods, l as PostHogObservabilityClient, ln as MeProfile, lr as StateLabel, lt as PermissionRevokeInput, m as IdentityProfileDetails, mn as OfframpStatus, mr as InvocationControls, mt as OrgMeOptions, nn as DestinationListInput, nt as OrganizationPaymentsMethods, o as ObservationDelivery, on as DestinationsMethods, or as IdentityState, ot as PermissionMethods, p as CreateCapxulClientInput, pn as OfframpQuoteInput, pr as IdentityTransition, pt as OrgMeMethod, q as PayrollOptions, qn as Permission, qt as ActivityReference, r as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, rn as DestinationPayload, rr as TelemetryPort, rt as PermissionAssignInput, s as SdkFailureObservation, sn as FinancialOpsMethods, sr as OrgLane, st as PermissionOptions, t as CapxulClientInput, tn as DestinationKind, tt as OrganizationPaymentItemInput, u as PostHogObservabilityOptions, un as MovementActivityEvidence, ur as destination, ut as PermissionReadResult, v as Holding, vn as PaymentDocumentKind, vt as OrganizationOnboarding, w as CreateOrgInput, wn as PaymentStatus, wt as ActorProfile, x as MediaMethods, xn as PaymentDocumentVerification, xt as PersonOnboardingInput, y as SystemMethods, yn as PaymentDocumentRef, yt as OrganizationOnboardingInput, z as AuthorizeRunInput, zn as IdentityMethods, zt as ActivityDetail } from "./production-Cn5awmW8.mjs";
3
3
  import { Hex } from "viem";
4
4
  import { Effect, Layer } from "effect";
5
5
  //#region ../errors/src/index.d.ts
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as signerFailure, B as isRestoring, C as normalizeExceptionErrorKind, E as safeExceptionLabel, F as CAPXUL_OPERATIONS, I as isCapxulOperation, L as normalizeCapxulOperation, M as PAYMENT_DIRECTIONS, N as PAYMENT_STATUSES, O as isSettingUpLifecycle, R as destination, S as SDK_VERSION, T as projectSdkException, _ as fingerprintPaymentIntent, b as failureDetail, d as embeddedSigner, f as openfortEmbeddedSigner, g as devPrivateKeySigner, h as deriveDevPrivateKey, j as resolveFailureMode, k as injectedWalletSigner, m as openfortEmbeddedWalletPort, p as openfortEmbeddedSignerFromWallet, r as postHogObservability, t as createCapxulClient$1, w as normalizeExceptionOperation, x as EXCEPTION_MESSAGE, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as isClaimed } from "./production-BmsFhDUr.mjs";
1
+ import { A as signerFailure, B as isRestoring, C as normalizeExceptionErrorKind, E as safeExceptionLabel, F as CAPXUL_OPERATIONS, I as isCapxulOperation, L as normalizeCapxulOperation, M as PAYMENT_DIRECTIONS, N as PAYMENT_STATUSES, O as isSettingUpLifecycle, R as destination, S as SDK_VERSION, T as projectSdkException, _ as fingerprintPaymentIntent, b as failureDetail, d as embeddedSigner, f as openfortEmbeddedSigner, g as devPrivateKeySigner, h as deriveDevPrivateKey, j as resolveFailureMode, k as injectedWalletSigner, m as openfortEmbeddedWalletPort, p as openfortEmbeddedSignerFromWallet, r as postHogObservability, t as createCapxulClient$1, w as normalizeExceptionOperation, x as EXCEPTION_MESSAGE, y as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, z as isClaimed } from "./production-BQVXgcbY.mjs";
2
2
  import { A as currencySymbolFor, B as toCurrencyCode, G as toHandle, M as toAddress, T as EVM_ADDRESS_RE, Y as toPartyId, at as HANDLE_RE, b as configuredMoneyAssetById, ct as CapxulError, dt as isCapxulError, ft as CHAIN_UPSTREAMS, g as CAPXUL_PAYMENTS_V2_ADDRESS, k as assetIdFor, pt as FAILURE_MODES, st as CAPXUL_ERROR_CODES, ut as Errors, z as toCountryCode } from "./OAuthBearerAuthClient-DDD0JlaI.mjs";
3
3
  import { formatUnits } from "viem";
4
4
  import { Effect } from "effect";
@@ -5894,6 +5894,9 @@ const moneyExecutionContract = {
5894
5894
  //#region src/ports/indexer-read.ts
5895
5895
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
5896
5896
  const INDEXER_MAX_LIMIT = 1e3;
5897
+ function validTime(value) {
5898
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
5899
+ }
5897
5900
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5898
5901
  /**
5899
5902
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -5901,12 +5904,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
5901
5904
  * against the hermetic adapter cannot pass on input production rejects. Throws
5902
5905
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
5903
5906
  */
5907
+ function assertEventLookup(input, id) {
5908
+ if (input.limit !== 1 || (input.offset ?? 0) !== 0 || input.timeline !== void 0 || input.settlements !== void 0 || !/^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(id)) throw Errors.invalidInput("eventId", "must identify one logical event without another cursor");
5909
+ }
5904
5910
  function assertIndexerPage(input) {
5905
5911
  const safe = String(input.safe).toLowerCase();
5906
5912
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
5907
5913
  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}`);
5908
5914
  const offset = input.offset ?? 0;
5909
5915
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
5916
+ const timeline = input.timeline;
5917
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
5918
+ if (input.settlements !== void 0 && (offset !== 0 || timeline !== void 0 || input.settlements.length < 1 || input.settlements.length > 50 || !input.settlements.every((id) => /^0x[0-9a-f]{64}$/.test(id)))) throw Errors.invalidInput("settlements", "must hold 1 to 50 settlement IDs without another cursor");
5919
+ if (timeline !== void 0) {
5920
+ if (offset !== 0 || !validTime(timeline.from) || !validTime(timeline.to) || timeline.from > timeline.to || timeline.before !== void 0 && (!validTime(timeline.before.timestamp) || !/^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(timeline.before.id))) throw Errors.invalidInput("timeline", "must be a bounded time window with a logical event cursor and no offset");
5921
+ }
5910
5922
  return safe;
5911
5923
  }
5912
5924
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -5936,20 +5948,6 @@ function directionFor(safe, transfer) {
5936
5948
  if (from === safe && to === safe) return "self";
5937
5949
  return to === safe ? "in" : "out";
5938
5950
  }
5939
- /**
5940
- * The Transfer a protocol Payment moved is the same money as its `payment`
5941
- * row. One actor sees one item for it, so the Transfer is claimed and does not
5942
- * become a second row. Another transfer in the same transaction that this
5943
- * payment did not move stays unclaimed and keeps its own row.
5944
- */
5945
- function claimedTransferIds(transfers, payments) {
5946
- const claimed = /* @__PURE__ */ new Set();
5947
- for (const payment of payments) {
5948
- const match = transfers.find((transfer) => !claimed.has(transfer.id) && transfer.txHash === payment.txHash && transfer.token.toLowerCase() === payment.token.toLowerCase() && transfer.amount === payment.amount && transfer.to.toLowerCase() === payment.recipient.toLowerCase());
5949
- if (match !== void 0) claimed.add(match.id);
5950
- }
5951
- return claimed;
5952
- }
5953
5951
  function withAnnotation(item, annotation) {
5954
5952
  return annotation?.counterpartyLabel === void 0 ? item : {
5955
5953
  ...item,
@@ -5960,10 +5958,14 @@ function withAnnotation(item, annotation) {
5960
5958
  * An indexed settlement confirms the intent. A missing row can be outside
5961
5959
  * this page or still backfilling; only the correction owner can prove rollback.
5962
5960
  */
5963
- function confirmPayment(intent, chain) {
5964
- if (chain !== void 0) return {
5961
+ function confirmPayment(intent, chain, expected) {
5962
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
5965
5963
  ...intent,
5966
5964
  status: "settled",
5965
+ ...intent.execution === void 0 ? {} : { execution: {
5966
+ status: "settled",
5967
+ retryRequired: false
5968
+ } },
5967
5969
  receipt: {
5968
5970
  chainId: chain.chainId,
5969
5971
  transactionHash: chain.txHash
@@ -5972,16 +5974,22 @@ function confirmPayment(intent, chain) {
5972
5974
  };
5973
5975
  return intent;
5974
5976
  }
5977
+ function matchesPaymentFact(chain, expected) {
5978
+ return chain !== void 0 && expected !== void 0 && chain.chainId === expected.chainId && chain.contract.toLowerCase() === expected.contract.toLowerCase() && chain.sender.toLowerCase() === expected.sender.toLowerCase() && chain.recipient.toLowerCase() === expected.recipient.toLowerCase() && chain.token.toLowerCase() === expected.token.toLowerCase() && chain.amount.toString() === expected.rawAmount && chain.documentHash.toLowerCase() === expected.documentHash.toLowerCase() && chain.kind === expected.kind;
5979
+ }
5975
5980
  /**
5976
5981
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
5977
5982
  * denominate its asset. The indexer serves only configured assets, so a row
5978
5983
  * the registry does not know means the SDK and that deployment disagree; it is
5979
5984
  * dropped rather than shown with a guessed currency.
5980
5985
  */
5986
+ function isActivityVisible(item) {
5987
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
5988
+ }
5981
5989
  function transferItem(safe, transfer, annotation) {
5982
5990
  const asset = configuredMoneyAssetById(transfer.assetId);
5983
5991
  if (asset === null) return null;
5984
- const direction = directionFor(safe, transfer);
5992
+ const direction = directionFor(safe.toLowerCase(), transfer);
5985
5993
  const counterparty = direction === "in" ? transfer.from : transfer.to;
5986
5994
  const at = blockMillis(transfer);
5987
5995
  return withAnnotation({
@@ -6003,47 +6011,6 @@ function transferItem(safe, transfer, annotation) {
6003
6011
  updatedAt: at
6004
6012
  }, annotation);
6005
6013
  }
6006
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
6007
- function compareItems(left, right) {
6008
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
6009
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
6010
- if (left.id === right.id) return 0;
6011
- return left.id < right.id ? 1 : -1;
6012
- }
6013
- function stitchActivity(input) {
6014
- const safe = input.safe.toLowerCase();
6015
- const chainBySettlement = /* @__PURE__ */ new Map();
6016
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
6017
- const chainByPaymentId = /* @__PURE__ */ new Map();
6018
- for (const link of input.links) {
6019
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
6020
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
6021
- }
6022
- const annotations = /* @__PURE__ */ new Map();
6023
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
6024
- const items = [];
6025
- const claiming = [];
6026
- for (const intent of input.intents) {
6027
- if (intent.kind !== "payment") continue;
6028
- const chain = chainByPaymentId.get(intent.id);
6029
- if (chain !== void 0) claiming.push(chain);
6030
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
6031
- }
6032
- const claimed = claimedTransferIds(input.transfers, claiming);
6033
- for (const transfer of input.transfers) {
6034
- if (claimed.has(transfer.id)) continue;
6035
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
6036
- if (item !== null) items.push(item);
6037
- }
6038
- return items.sort(compareItems);
6039
- }
6040
- /** The reference ids one stitched page needs its Convex overlay for. */
6041
- function overlayReferences(items) {
6042
- return items.map((item) => ({
6043
- kind: item.kind,
6044
- id: item.id
6045
- }));
6046
- }
6047
6014
  /**
6048
6015
  * Money in, money out and the pending share, one bucket per currency and
6049
6016
  * precision. Two assets that peg to the same currency at different precision
@@ -6062,7 +6029,7 @@ function summarizeItems(items) {
6062
6029
  };
6063
6030
  const raw = rawUnits(item.amount.value, item.amount.decimals);
6064
6031
  if (item.kind === "payment" && item.status !== "settled") {
6065
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
6032
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
6066
6033
  } else if (item.direction === "in") bucket.in += raw;
6067
6034
  else if (item.direction === "out") bucket.out += raw;
6068
6035
  buckets.set(key, bucket);
@@ -6085,179 +6052,412 @@ function rawUnits(value, decimals) {
6085
6052
  }
6086
6053
  //#endregion
6087
6054
  //#region src/surface/activity-read.ts
6088
- /** How many Convex intent rows one page reads for its labels and pending rows. */
6089
- const INTENT_WINDOW = 100;
6090
- /** movement/annotations:overlay bounds each identifier array to 200. */
6055
+ const BATCH = 100;
6091
6056
  const MAX_OVERLAY_IDS = 200;
6092
- /** The window a summary aggregates over. The indexer's own page bound. */
6093
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
6094
- function parseCursor(value) {
6095
- if (value === void 0) return 0;
6096
- try {
6097
- const parsed = JSON.parse(value);
6098
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
6099
- } catch {
6100
- return 0;
6101
- }
6102
- }
6103
- function inRange(item, range) {
6104
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
6105
- return range?.to === void 0 || item.createdAt <= range.to;
6057
+ function actorKey(actor) {
6058
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
6059
+ }
6060
+ function scopeKey(scope) {
6061
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
6062
+ }
6063
+ function selectionKey(params) {
6064
+ const filter = params?.filter;
6065
+ return JSON.stringify([
6066
+ params?.range?.from ?? null,
6067
+ params?.range?.to ?? null,
6068
+ filter?.kind ?? null,
6069
+ filter?.direction ?? null,
6070
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
6071
+ ]);
6072
+ }
6073
+ function validString(value, max) {
6074
+ return typeof value === "string" && value.length <= max;
6075
+ }
6076
+ function validPosition(position) {
6077
+ return position === null || position !== void 0 && typeof position === "object" && Number.isSafeInteger(position.timestamp) && position.timestamp >= 0 && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(position.id);
6078
+ }
6079
+ function parseCursor(value, scope, selection) {
6080
+ if (value === void 0) return null;
6081
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
6082
+ const cursor = JSON.parse(value);
6083
+ if (cursor === null || typeof cursor !== "object" || cursor.version !== 1 || cursor.scope !== scope || cursor.selection !== selection || !Number.isSafeInteger(cursor.checkpoint) || cursor.checkpoint < 0 || cursor.source !== "indexed" && cursor.source !== "fallback" || cursor.paymentCursor !== null && !validString(cursor.paymentCursor, 2048) || typeof cursor.paymentDone !== "boolean" || typeof cursor.transferDone !== "boolean" || typeof cursor.degraded !== "boolean" || !Array.isArray(cursor.paymentPending) || cursor.paymentPending.length > BATCH || new Set(cursor.paymentPending).size !== cursor.paymentPending.length || !cursor.paymentPending.every((id) => validString(id, 256)) || !validPosition(cursor.transferBefore)) throw Errors.invalidInput("cursor", "Activity scope or filters changed; restart from the first page");
6084
+ return cursor;
6085
+ }
6086
+ function requireParams(params) {
6087
+ const limit = params?.limit ?? 25;
6088
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
6089
+ for (const value of [params?.range?.from, params?.range?.to]) if (value !== void 0 && !Number.isSafeInteger(value)) throw Errors.invalidInput("range", "must contain integer epoch milliseconds");
6090
+ const f = params?.filter;
6091
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
6092
+ "in",
6093
+ "out",
6094
+ "self"
6095
+ ].includes(f.direction) || f?.status !== void 0 && (!Array.isArray(f.status) || !f.status.every((status) => PAYMENT_STATUSES.includes(status)))) throw Errors.invalidInput("filter", "invalid Activity filter");
6106
6096
  }
6107
- /** The caller's filter applied to the merged page. The join owns the order. */
6108
6097
  function matchesFilter(item, filter) {
6109
- if (filter === void 0) return true;
6110
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
6111
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
6112
- if (filter.status === void 0) return true;
6113
- return item.kind === "payment" && filter.status.includes(item.status);
6098
+ return (filter?.kind === void 0 || item.kind === filter.kind) && (filter?.direction === void 0 || item.direction === filter.direction) && (filter?.status === void 0 || item.kind === "payment" && filter.status.includes(item.status));
6114
6099
  }
6115
6100
  function overlay(deps, actor, controls, input) {
6116
6101
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
6117
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
6102
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
6118
6103
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
6119
6104
  ...actor === void 0 ? {} : { actor },
6120
6105
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
6121
6106
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
6122
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
6123
- ...pages[0],
6124
- links: pages.flatMap((page) => page.links),
6125
- annotations: pages.flatMap((page) => page.annotations)
6126
- })));
6107
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
6108
+ const first = pages[0];
6109
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
6110
+ return Effect.succeed({
6111
+ ...first,
6112
+ links: pages.flatMap((page) => page.links),
6113
+ annotations: pages.flatMap((page) => page.annotations),
6114
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
6115
+ });
6116
+ }));
6127
6117
  }
6128
6118
  function convexPage(deps, actor, controls, input) {
6129
6119
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
6130
- ...actor === void 0 ? {} : { actor },
6131
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
6132
- ...input.limit === void 0 ? {} : { limit: input.limit },
6133
- ...input.range === void 0 ? {} : { range: input.range }
6120
+ ...input,
6121
+ actor
6134
6122
  } }));
6135
6123
  }
6136
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
6137
- function chainRows(deps, safe, limit) {
6138
- const page = {
6139
- safe,
6140
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
6141
- };
6142
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
6143
- transfers,
6144
- payments
6145
- })), Effect.catch(() => Effect.succeed(null)));
6124
+ function chainRead(deps, table, input) {
6125
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
6126
+ if (error.publicError.details?.reason === "scope-refused" || ![
6127
+ "NETWORK_ERROR",
6128
+ "RATE_LIMITED",
6129
+ "PROVIDER_ERROR",
6130
+ "CAPABILITY_UNAVAILABLE",
6131
+ "TIMEOUT"
6132
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
6133
+ return Effect.succeed(null);
6134
+ }));
6146
6135
  }
6147
- /** The stitched, filtered rows of one window, newest first. */
6148
- function stitchedWindow(deps, actor, controls, window, params) {
6136
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
6149
6137
  return Effect.gen(function* () {
6150
- const scope = yield* overlay(deps, actor, controls, {});
6151
- const convex = yield* convexPage(deps, actor, controls, {
6152
- limit: INTENT_WINDOW,
6153
- ...params.range === void 0 ? {} : { range: params.range }
6138
+ const page = yield* convexPage(deps, scope.actor, controls, {
6139
+ limit: params?.limit ?? 25,
6140
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
6141
+ range: {
6142
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
6143
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
6144
+ },
6145
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
6154
6146
  });
6155
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
6156
- if (rows === null || scope.safe === null) {
6157
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
6158
- read_source: "convex",
6159
- read_warning: "indexer-unavailable"
6160
- });
6161
- return {
6162
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
6163
- stitched: false,
6164
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
6165
- };
6147
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
6148
+ kind: item.kind,
6149
+ id: item.id
6150
+ })) });
6151
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
6152
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
6153
+ yield* Effect.annotateCurrentSpan({
6154
+ read_source: "convex",
6155
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
6156
+ });
6157
+ return {
6158
+ ...page,
6159
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
6160
+ checkpoint: state.checkpoint,
6161
+ cursor: page.cursor === null ? null : JSON.stringify({
6162
+ ...state,
6163
+ source: "fallback",
6164
+ paymentCursor: page.cursor,
6165
+ degraded
6166
+ }),
6167
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
6168
+ };
6169
+ });
6170
+ }
6171
+ function boundedRange(range, checkpoint) {
6172
+ return {
6173
+ ...range?.from === void 0 ? {} : { from: range.from },
6174
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
6175
+ };
6176
+ }
6177
+ function unavailablePage(deps, scope, controls, params, state, first) {
6178
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
6179
+ return Effect.annotateCurrentSpan({
6180
+ read_source: "indexer",
6181
+ read_warning: "indexer-unavailable"
6182
+ }).pipe(Effect.as({
6183
+ items: [],
6184
+ cursor: null,
6185
+ checkpoint: state.checkpoint,
6186
+ observedAt: Date.now(),
6187
+ readWarning: "indexer-unavailable"
6188
+ }));
6189
+ }
6190
+ function readTransfers(deps, safe, range, state, wanted, first) {
6191
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
6192
+ const to = Math.floor(range.to / 1e3);
6193
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
6194
+ return chainRead(deps, "transfer", {
6195
+ safe: toAddress(safe),
6196
+ limit: wanted ? BATCH : 1,
6197
+ timeline: {
6198
+ from,
6199
+ to,
6200
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
6166
6201
  }
6167
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6168
- const decoration = yield* overlay(deps, actor, controls, {
6169
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
6202
+ });
6203
+ }
6204
+ function readPayments(deps, scope, controls, range, state, wanted) {
6205
+ const done = state.paymentDone || !wanted;
6206
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
6207
+ items: [],
6208
+ pending: state.paymentPending,
6209
+ cursor: state.paymentCursor,
6210
+ done
6211
+ });
6212
+ return convexPage(deps, scope.actor, controls, {
6213
+ limit: BATCH,
6214
+ range,
6215
+ filter: { kind: "payment" },
6216
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
6217
+ }).pipe(Effect.map((page) => ({
6218
+ items: page.items,
6219
+ pending: page.items.map((item) => item.id),
6220
+ cursor: page.cursor,
6221
+ done: page.cursor === null
6222
+ })));
6223
+ }
6224
+ function readPaymentFacts(deps, safe, settlements) {
6225
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
6226
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
6227
+ safe: toAddress(safe),
6228
+ limit: 50,
6229
+ settlements: settlements.slice(offset, offset + 50)
6230
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
6231
+ }
6232
+ function decoratePage(deps, scope, controls, transfers, page, range) {
6233
+ return Effect.gen(function* () {
6234
+ const decoration = yield* overlay(deps, scope.actor, controls, {
6235
+ references: [...page.pending.map((id) => ({
6236
+ kind: "payment",
6237
+ id
6238
+ })), ...transfers.map((row) => ({
6170
6239
  kind: "movement",
6171
6240
  id: row.id
6172
6241
  }))],
6173
- settlementIds: rows.payments.map((row) => row.settlementId)
6242
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
6174
6243
  });
6175
- const intents = [...convex.items];
6176
- const missing = new Set(decoration.links.map((link) => link.paymentId));
6177
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
6178
- let cursor = convex.cursor;
6179
- while (missing.size > 0 && cursor !== null) {
6180
- const page = yield* convexPage(deps, actor, controls, {
6181
- limit: INTENT_WINDOW,
6182
- cursor,
6183
- ...params.range === void 0 ? {} : { range: params.range }
6184
- });
6185
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
6186
- cursor = page.cursor;
6244
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
6245
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
6246
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
6247
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
6248
+ if (facts === null) return null;
6249
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
6250
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
6251
+ fact: bySettlement.get(link.settlementId),
6252
+ expected: link.expected
6253
+ }]));
6254
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
6255
+ return {
6256
+ payments: page.pending.flatMap((id) => {
6257
+ const item = byId.get(id);
6258
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
6259
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
6260
+ return isActivityVisible(confirmed) ? [confirmed] : [];
6261
+ }),
6262
+ annotations,
6263
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
6264
+ };
6265
+ });
6266
+ }
6267
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
6268
+ const { payments, annotations, linked } = decoration;
6269
+ let pi = 0;
6270
+ let ti = 0;
6271
+ const items = [];
6272
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
6273
+ let transferBefore = state.transferBefore;
6274
+ while (items.length < limit) {
6275
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
6276
+ const payment = payments[pi];
6277
+ const row = transfers[ti];
6278
+ if (payment === void 0 && row === void 0) break;
6279
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
6280
+ pi += 1;
6281
+ if (matchesFilter(payment, filter)) items.push(payment);
6282
+ } else if (row !== void 0) {
6283
+ ti += 1;
6284
+ transferBefore = {
6285
+ timestamp: Number(row.timestamp),
6286
+ id: row.id
6287
+ };
6288
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
6289
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
6290
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
6291
+ }
6292
+ }
6293
+ return {
6294
+ items,
6295
+ next: {
6296
+ ...state,
6297
+ paymentCursor: page.cursor,
6298
+ paymentDone: page.done,
6299
+ paymentPending: payments.slice(pi).map((item) => item.id),
6300
+ transferBefore,
6301
+ transferDone: ti === transfers.length && transferEnd
6187
6302
  }
6188
- const older = intents.slice(convex.items.length);
6189
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
6303
+ };
6304
+ }
6305
+ function indexedPage(deps, scope, controls, params, state, first) {
6306
+ return Effect.gen(function* () {
6307
+ const range = boundedRange(params?.range, state.checkpoint);
6308
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
6309
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
6310
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
6311
+ const transfers = wantsTransfers ? found : [];
6312
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
6313
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
6314
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
6315
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
6316
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
6317
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
6318
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6190
6319
  return {
6191
- items: stitchActivity({
6192
- safe: scope.safe,
6193
- transfers: rows.transfers,
6194
- payments: rows.payments,
6195
- intents,
6196
- links: decoration.links,
6197
- annotations: [...decoration.annotations, ...olderDecoration]
6198
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
6199
- stitched: true
6320
+ items,
6321
+ cursor,
6322
+ checkpoint: state.checkpoint,
6323
+ observedAt: Date.now()
6200
6324
  };
6201
6325
  });
6202
6326
  }
6203
6327
  function readActivityPage(deps, actor, controls, params) {
6204
- const limit = params?.limit ?? 25;
6205
- const offset = parseCursor(params?.cursor);
6206
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
6207
- ...params?.range === void 0 ? {} : { range: params.range },
6208
- ...params?.filter === void 0 ? {} : { filter: params.filter }
6209
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
6210
- const observedAt = Date.now();
6211
- if (!stitched) return {
6212
- items: items.slice(0, limit),
6328
+ return Effect.gen(function* () {
6329
+ yield* Effect.try({
6330
+ try: () => requireParams(params),
6331
+ catch: (cause) => ({ publicError: cause })
6332
+ });
6333
+ const scope = yield* overlay(deps, actor, controls, {});
6334
+ const selection = selectionKey(params);
6335
+ const cursor = yield* Effect.try({
6336
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
6337
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
6338
+ });
6339
+ const state = cursor ?? {
6340
+ version: 1,
6341
+ scope: scopeKey(scope),
6342
+ selection,
6343
+ checkpoint: Date.now(),
6344
+ source: "indexed",
6345
+ paymentCursor: null,
6346
+ paymentDone: false,
6347
+ paymentPending: [],
6348
+ transferBefore: null,
6349
+ transferDone: false,
6350
+ degraded: false
6351
+ };
6352
+ const range = boundedRange(params?.range, state.checkpoint);
6353
+ if (range.from !== void 0 && range.from > range.to) return {
6354
+ items: [],
6213
6355
  cursor: null,
6214
- checkpoint: observedAt,
6215
- observedAt,
6216
- ...readWarning === void 0 ? {} : { readWarning }
6356
+ checkpoint: state.checkpoint,
6357
+ observedAt: Date.now()
6217
6358
  };
6359
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
6360
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
6361
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
6362
+ });
6363
+ }
6364
+ function readActivitySummary(deps, actor, controls, params) {
6365
+ return Effect.gen(function* () {
6366
+ const totals = /* @__PURE__ */ new Map();
6367
+ let cursor;
6368
+ let count = 0;
6369
+ let readWarning;
6370
+ do {
6371
+ const page = yield* readActivityPage(deps, actor, controls, {
6372
+ limit: BATCH,
6373
+ ...cursor === void 0 ? {} : { cursor },
6374
+ ...params?.window === void 0 ? {} : { range: params.window }
6375
+ });
6376
+ count += page.items.length;
6377
+ readWarning ??= page.readWarning;
6378
+ for (const total of summarizeItems(page.items)) {
6379
+ const key = `${total.currency}:${total.decimals}`;
6380
+ const old = totals.get(key);
6381
+ totals.set(key, old === void 0 ? total : {
6382
+ ...total,
6383
+ in: String(BigInt(old.in) + BigInt(total.in)),
6384
+ out: String(BigInt(old.out) + BigInt(total.out)),
6385
+ net: String(BigInt(old.net) + BigInt(total.net)),
6386
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
6387
+ });
6388
+ }
6389
+ cursor = page.cursor ?? void 0;
6390
+ } while (cursor !== void 0);
6218
6391
  return {
6219
- items: items.slice(offset, offset + limit),
6220
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
6221
- checkpoint: observedAt,
6222
- observedAt
6392
+ window: {
6393
+ from: params?.window?.from ?? null,
6394
+ to: params?.window?.to ?? null
6395
+ },
6396
+ totals: [...totals.values()],
6397
+ count,
6398
+ observedAt: Date.now(),
6399
+ ...readWarning === void 0 ? {} : { readWarning }
6223
6400
  };
6224
- }));
6401
+ });
6225
6402
  }
6226
- function readActivitySummary(deps, actor, controls, params) {
6227
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
6228
- window: {
6229
- from: params?.window?.from ?? null,
6230
- to: params?.window?.to ?? null
6231
- },
6232
- totals: summarizeItems(items),
6233
- count: items.length,
6234
- observedAt: Date.now(),
6235
- ...readWarning === void 0 ? {} : { readWarning }
6236
- })));
6403
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
6404
+ function readIndexedMovement(deps, actor, controls, eventId) {
6405
+ return Effect.gen(function* () {
6406
+ const scope = yield* overlay(deps, actor, controls, { references: [{
6407
+ kind: "movement",
6408
+ id: eventId
6409
+ }] });
6410
+ if (scope.safe === null) return null;
6411
+ const safe = scope.safe.toLowerCase();
6412
+ const row = (yield* deps.indexer.read("transfer", {
6413
+ safe: toAddress(safe),
6414
+ limit: 1,
6415
+ eventId
6416
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
6417
+ if (row === void 0) return null;
6418
+ const blockNumber = Number(row.blockNumber);
6419
+ if (row.id !== eventId || row.from.toLowerCase() !== safe && row.to.toLowerCase() !== safe || !Number.isSafeInteger(blockNumber) || blockNumber < 0 || !Number.isSafeInteger(row.logIndex) || row.logIndex < 0) return yield* Effect.fail({ publicError: Errors.invalidInput("activity", "indexed Movement does not match the requested scope or evidence") });
6420
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
6421
+ return {
6422
+ kind: "movement",
6423
+ movementId: row.id,
6424
+ viewer: scope.actor,
6425
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
6426
+ evidence: {
6427
+ chainId: row.chainId,
6428
+ contractAddress: row.token,
6429
+ tokenAddress: row.token,
6430
+ transactionHash: row.txHash,
6431
+ blockNumber,
6432
+ blockHash: row.blockHash,
6433
+ inclusion: "included",
6434
+ verification: "verified",
6435
+ canonicalState: "finalized",
6436
+ transferLogIndex: row.logIndex,
6437
+ source: row.from,
6438
+ destination: row.to,
6439
+ rawAmount: row.amount.toString(),
6440
+ link: {
6441
+ movementId: row.id,
6442
+ depositPaymentId: null
6443
+ }
6444
+ }
6445
+ };
6446
+ });
6237
6447
  }
6238
- /**
6239
- * Tell the caller when the indexed page for this Safe changed.
6240
- *
6241
- * The transport is the indexer's one SSE connection, wired in the Ponder
6242
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
6243
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
6244
- * the caller re-reads.
6245
- */
6246
6448
  function subscribeActivity(deps, actor, controls, onChange) {
6247
6449
  return Effect.gen(function* () {
6248
6450
  const scope = yield* overlay(deps, actor, controls, {});
6249
6451
  if (scope.safe === null) return () => {};
6250
- const page = {
6251
- safe: scope.safe,
6252
- limit: INTENT_WINDOW
6253
- };
6254
6452
  let delivered = false;
6255
- const notify = () => {
6256
- if (delivered) onChange();
6257
- delivered = true;
6258
- };
6259
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
6260
- if (snapshot.status === "ok") notify();
6453
+ return yield* deps.indexer.subscribe("transfer", {
6454
+ safe: toAddress(scope.safe),
6455
+ limit: BATCH
6456
+ }, (snapshot) => {
6457
+ if (snapshot.status === "ok") {
6458
+ if (delivered) onChange();
6459
+ delivered = true;
6460
+ }
6261
6461
  if (snapshot.status === "error") {
6262
6462
  delivered = true;
6263
6463
  onChange();
@@ -6267,7 +6467,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
6267
6467
  }
6268
6468
  //#endregion
6269
6469
  //#region package.json
6270
- var version = "4.20.0-beta.1";
6470
+ var version = "4.20.0-beta.2";
6271
6471
  //#endregion
6272
6472
  //#region src/telemetry/exception-projection.ts
6273
6473
  /** Fixed fallback for failures that have no safe message. */
@@ -7833,7 +8033,10 @@ function makeFinancialOpsMethods(deps) {
7833
8033
  ...params?.limit === void 0 ? {} : { limit: params.limit },
7834
8034
  ...params?.range === void 0 ? {} : { range: params.range },
7835
8035
  ...params?.filter === void 0 ? {} : { filter: params.filter }
7836
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
8036
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
8037
+ ...page,
8038
+ items: page.items.filter(isActivityVisible)
8039
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
7837
8040
  },
7838
8041
  summary: (params, options) => {
7839
8042
  const controls = deps.invocationControls?.(options) ?? options;
@@ -7854,7 +8057,10 @@ function makeFinancialOpsMethods(deps) {
7854
8057
  actor: scope,
7855
8058
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
7856
8059
  });
7857
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
8060
+ }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => indexerDeps !== void 0 && reference.kind === "movement" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(reference.id) ? readIndexedMovement(indexerDeps, actor?.kind === "org" ? {
8061
+ kind: "organization",
8062
+ orgId: actor.orgId
8063
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
7858
8064
  input: {
7859
8065
  kind: reference.kind,
7860
8066
  id: reference.id,
@@ -15260,7 +15466,8 @@ function decodeTransfer(value) {
15260
15466
  token: toAddress(hex(row, "token")),
15261
15467
  from: toAddress(hex(row, "from")),
15262
15468
  to: toAddress(hex(row, "to")),
15263
- amount: big(row, "amount")
15469
+ amount: big(row, "amount"),
15470
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
15264
15471
  };
15265
15472
  }
15266
15473
  function decodePayment(value) {
@@ -15346,6 +15553,60 @@ const ACTOR_PARAMS = {
15346
15553
  */
15347
15554
  function ponderQuery(table, input) {
15348
15555
  const safe = assertIndexerPage(input);
15556
+ if (input.eventId !== void 0) {
15557
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
15558
+ return {
15559
+ sql: `${HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\""))} from "transfer" where (("transfer"."from" = $1 or "transfer"."to" = $2) and "transfer"."id" = $3) limit $4`,
15560
+ params: [
15561
+ safe,
15562
+ safe,
15563
+ input.eventId,
15564
+ 1
15565
+ ],
15566
+ typings: [
15567
+ "none",
15568
+ "none",
15569
+ "none",
15570
+ "none"
15571
+ ]
15572
+ };
15573
+ }
15574
+ if (input.settlements !== void 0) {
15575
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
15576
+ const params = [
15577
+ safe,
15578
+ safe,
15579
+ ...input.settlements,
15580
+ input.limit
15581
+ ];
15582
+ return {
15583
+ sql: `${HEADS.payment.slice(0, HEADS.payment.indexOf("where "))}where (("payment"."sender" = $1 or "payment"."recipient" = $2) and "payment"."settlement_id" in (${input.settlements.map((_, i) => `$${i + 3}`).join(", ")})) limit $${params.length}`,
15584
+ params,
15585
+ typings: params.map(() => "none")
15586
+ };
15587
+ }
15588
+ if (input.timeline !== void 0) {
15589
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
15590
+ const { from, to, before } = input.timeline;
15591
+ const params = [
15592
+ safe,
15593
+ safe,
15594
+ from,
15595
+ to
15596
+ ];
15597
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
15598
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
15599
+ if (before !== void 0) {
15600
+ params.push(before.timestamp, before.timestamp, before.id);
15601
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
15602
+ }
15603
+ params.push(input.limit);
15604
+ return {
15605
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
15606
+ params,
15607
+ typings: params.map(() => "none")
15608
+ };
15609
+ }
15349
15610
  const offset = input.offset ?? 0;
15350
15611
  const actors = ACTOR_PARAMS[table];
15351
15612
  const params = [];
@@ -332,6 +332,13 @@ declare const PAYMENT_STATUSES: readonly ["pending", "settling", "unresolved", "
332
332
  type PaymentStatus$1 = (typeof PAYMENT_STATUSES)[number];
333
333
  declare const PAYMENT_EXECUTION_STATUSES: readonly ["pending", "included", "dropped", "replaced", "expired", "unresolved", "superseded", "failed", "settled"];
334
334
  type PaymentExecutionStatus = (typeof PAYMENT_EXECUTION_STATUSES)[number];
335
+ /** Read-time execution meaning; it does not change the Payment's durable outcome. */
336
+ interface PaymentExecutionView {
337
+ readonly status: PaymentExecutionStatus;
338
+ readonly retryRequired: boolean;
339
+ /** Present only when complete durable execution history supports no broadcast. */
340
+ readonly notSent?: true;
341
+ }
335
342
  /** The payment direction relative to the current actor. */
336
343
  declare const PAYMENT_DIRECTIONS: readonly ["in", "out", "self"];
337
344
  type PaymentDirection$1 = (typeof PAYMENT_DIRECTIONS)[number];
@@ -927,6 +934,18 @@ interface IndexerReadInput {
927
934
  /** 1 to 1000 inclusive — the indexer's own bound. */
928
935
  readonly limit: number;
929
936
  readonly offset?: number;
937
+ readonly eventId?: string;
938
+ /** Exact Payment lookup for a bounded set of actor-authorized settlement IDs. */
939
+ readonly settlements?: readonly string[];
940
+ /** Transfer occurrence time in epoch seconds; continuation excludes the last row. */
941
+ readonly timeline?: {
942
+ readonly from: number;
943
+ readonly to: number;
944
+ readonly before?: {
945
+ readonly timestamp: number;
946
+ readonly id: string;
947
+ };
948
+ };
930
949
  }
931
950
  /**
932
951
  * The chain evidence every indexed row carries. `id` is the logical event id
@@ -950,6 +969,8 @@ interface IndexerEventIdentity {
950
969
  readonly timestamp: bigint;
951
970
  }
952
971
  interface IndexerTransferRow extends IndexerEventIdentity {
972
+ /** Timeline projection only. Null means no protocol event owns this Transfer. */
973
+ readonly paymentSettlementId?: string | null;
953
974
  readonly assetId: AssetId;
954
975
  readonly token: Address$1;
955
976
  readonly from: Address$1;
@@ -1390,10 +1411,7 @@ interface Payment {
1390
1411
  readonly timing: PaymentTiming;
1391
1412
  readonly released: PaymentMoney;
1392
1413
  readonly availableToClaim: PaymentMoney;
1393
- readonly execution?: {
1394
- readonly status: PaymentExecutionStatus;
1395
- readonly retryRequired: boolean;
1396
- };
1414
+ readonly execution?: PaymentExecutionView;
1397
1415
  readonly createdAt: number;
1398
1416
  readonly updatedAt: number;
1399
1417
  }
@@ -1543,6 +1561,7 @@ type ActivityReference = {
1543
1561
  };
1544
1562
  type ActivityItem = {
1545
1563
  readonly kind: "payment";
1564
+ readonly execution?: PaymentExecutionView;
1546
1565
  readonly id: string;
1547
1566
  readonly ledgerKind: "Payment" | "Payout" | "Deposit";
1548
1567
  readonly status: PaymentStatus;
@@ -1,5 +1,5 @@
1
1
  import { h as CapxulResult } from "../OAuthBearerAuthClient-C-ip-z8M.mjs";
2
- import { er as TelemetryEvent, f as CapxulClient, i as ObservationAdapter, n as ProductionSignerControls, nr as TelemetryIdentifyInput, pr as IdentityTransition, t as CapxulClientInput, tr as TelemetryGroupInput } from "../production-DvGDNIwu.mjs";
2
+ import { er as TelemetryEvent, f as CapxulClient, i as ObservationAdapter, n as ProductionSignerControls, nr as TelemetryIdentifyInput, pr as IdentityTransition, t as CapxulClientInput, tr as TelemetryGroupInput } from "../production-Cn5awmW8.mjs";
3
3
  import { Effect, Layer } from "effect";
4
4
  //#region src/testing/production-client.d.ts
5
5
  /** Create the one production client with per-client controls at its signing boundaries. */
@@ -1,4 +1,4 @@
1
- import { D as fromWei, P as redactTelemetryEvent, a as smartAccountErrorFromCapxul, c as identityErrorFromCapxul, i as assembleCapxulClient, l as convexCallErrorFromCapxul, n as createCapxulClientWithSignerControls, o as accountReadErrorFromCapxul, s as wireChainId, u as bootstrapErrorFromCapxul, v as toWei } from "../production-BmsFhDUr.mjs";
1
+ import { D as fromWei, P as redactTelemetryEvent, a as smartAccountErrorFromCapxul, c as identityErrorFromCapxul, i as assembleCapxulClient, l as convexCallErrorFromCapxul, n as createCapxulClientWithSignerControls, o as accountReadErrorFromCapxul, s as wireChainId, u as bootstrapErrorFromCapxul, v as toWei } from "../production-BQVXgcbY.mjs";
2
2
  import { G as toHandle, H as toEmail, I as toAuthUserId, K as toJwtToken, M as toAddress, N as toAllowedOrigin, P as toAppId, Q as toPublishableKey, R as toChainId, U as toEpochMs, V as toDurationMs, W as toEpochSeconds, ct as CapxulError, d as authClientPortFromPromiseAdapter, et as toSessionToken, it as validateHandle, j as toAccountId, n as readClockNow, q as toKycTier, r as InMemoryAuthCacheAdapter, ut as Errors, y as deriveCapxulSafeAddress, z as toCountryCode } from "../OAuthBearerAuthClient-DDD0JlaI.mjs";
3
3
  import { keccak256 } from "viem";
4
4
  import { Effect, Result, Semaphore } from "effect";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sdk",
3
- "version": "4.20.0-beta.1",
3
+ "version": "4.20.0-beta.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Xelmar-tech/infrastructure.git",
@@ -48,11 +48,11 @@
48
48
  "vite-plus": "0.3.0",
49
49
  "vitest": "4.1.11",
50
50
  "@capxul/config": "0.3.0",
51
- "@capxul/typescript-config": "0.0.0",
52
- "@capxul/types": "0.3.0",
53
51
  "@capxul/errors": "0.3.0",
52
+ "@capxul/types": "0.3.0",
53
+ "@capxul/typescript-config": "0.0.0",
54
54
  "@capxul/wire": "0.8.0-beta.0",
55
- "@capxul/observability": "4.20.0-beta.1"
55
+ "@capxul/observability": "4.20.0-beta.2"
56
56
  },
57
57
  "_permissionlessPinReason": "permissionless.toSafeSmartAccount is pinned to 0.3.4 for live Safe deployment E2E. Counterfactual address fixtures captured 2026-05-17 in packages/backend/convex/_shared/__tests__/counterfactual.test.ts and packages/config/tests/safe.test.ts must be re-verified before upgrading.",
58
58
  "scripts": {