@capxul/sdk 4.20.0-beta.24 → 4.20.0-beta.27
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/{access-CCRHHGfU.mjs → access-BnlgphFT.mjs} +1647 -221
- package/dist/{index-Q7zfSqaq.d.mts → index-BjDZXc7n.d.mts} +1045 -836
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/node/index.d.mts +1 -1
- package/dist/node/index.mjs +1 -1
- package/dist/{production-B6MQBe5R.mjs → production-D5uC-i8N.mjs} +24 -1199
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.mjs +2 -2
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $n as InvitationViewSchema, $r as SystemClockLayer, $t as version$1, A as registerProductTelemetryInvocation, An as details, At as safe4337ExecuteUserOpCallData, B as makeOrganizationPaymentsMethods, Bi as AuthCachePortTag, Br as runCapxulEffect, Bt as failureEvidenceProps, C as AccountReadPortTag, Cn as CLI_INVOCATION_KINDS, Ct as SEND_BATCH_SIGNATURE, D as convexCallErrorFromCapxul, Dn as isCapxulOperation, Dr as isEvmAddress, E as ConvexCallPortTag, Ei as toEpochMs, En as CAPXUL_OPERATIONS, Et as CONFIGURED_MONEY_ASSETS, Fi as FAILURE_MODES, Fn as replacements, Fr as publicIdentityFailure, Ft as SAFE_SENTINEL_OWNERS, G as makeActorRelationshipMethods, Gn as treasuries, Gr as toCapxulResult, Gt as observationContextProps, H as makePaymentsMethods, Hn as members, Hr as awaitCapxulResult, Ht as postHogFailureObservation, Ii as isFailureMode, In as PreparedPermissionExecutionSchema, Ir as runIdentityFacade, It as callOperation, J as makeTargetsMethods, Jn as ORGANIZATION_SETUP_REASONS, K as makeCheckoutMethods, Kn as workspaces, Kr as formatTraceparent, Kt as withCapturedAttemptContext, L as makeAccountMethods, Li as isRecord, Ln as PermissionCommandViewSchema, Lr as runPortEffect, Lt as asCapxulError, M as makeApprovalsMethods, Mi as Errors, Mn as authorities, Mr as reference, Mt as buildSafeMultiSendExecTransactionCall, N as makePayrollMethods, Ni as CapxulError, Nn as commands, Nr as makeAuthMethods, Nt as BASE_SEPOLIA_CHAIN_ID, O as retryIdempotentRead, Oi as toAuthUserId, On as normalizeCapxulOperation, Or as toAddress, Ot as deriveCapxulSafeAddress, P as makeUnavailablePayrollMethods, Pi as isCapxulError, Pn as configurations, Pr as askIdentity, Pt as MULTI_SEND_CALL_ONLY, Qn as toOrgId, R as emitDepositInitiated, Ri as readField, Rr as sessionFromActor, Rt as toPublicError, Sn as CLI_COMMAND_NAMES, St as SAFE_ENABLE_MODULE_ABI, T as TelemetryPortTag, Tn as stampTelemetryEnvelope, Tr as toExactAssetAmount, Tt as CAPXUL_PAYMENTS_V2_ADDRESS, U as waitForPaymentSubmission, Un as roles, Ur as defectAsCapxulError, Ut as observeFailedResult, V as makeOfframpMethods, Vn as invitations, Vr as withVerifiedOrganizationScope, W as makeDestinationsMethods, Wn as setups, Wr as runIfActive, Wt as observeSdkClient, Xr as carryInvocationParentSpan, Y as subscribeFreshQuery, Yn as ORGANIZATION_SETUP_STATUSES, Yr as ActorFailure, Zn as decodeChainCause, Zr as withInvocationParentSpan, _ as accountFromAccountPositions, _i as AuthClientError, _n as toPartyId, _r as toBudgetId, _t as ROLES_EXEC_TRANSACTION_WITH_ROLE_ABI, ai as observationRequestHeaders, an as isSignerNotReadySignal, ar as images, b as MONEY_READ_RATES, bi as globalLocalStorage, bn as redactTelemetryProps, br as toRoleKey, bt as ROLES_SET_UP_ABI, c as makePaymentDocumentsMethods, ci as readInvocationObservation, cn as signerFailure, cr as isBytes32, ct as roleKeyForPermission, d as SmartAccountPortTag, di as redactUrlSecrets, dn as makeClientRequestMethods, dr as toLowerBytes32, dt as CREATE_COMMITMENT_SIGNATURE, er as hashPermissionReplacementPreview, fi as isCredentialField, fn as toCountryCode, fr as toLowerHex, ft as MODULE_PROXY_FACTORY_ABI, g as ConvexAccountLayer, gn as toHandle, gr as EpochMsSchema, gt as ROLES_ASSIGN_ROLES_ABI, h as makeAccountsMethods, hi as toPublishableKey, hr as DurationMsSchema, ht as PARAMETER_TYPE, ii as BetterAuthBrowserLayer, in as isEcdsaSignatureHex, ir as AppIdSchema, it as canonicalJson, jn as onboardings, jr as absurd, jt as safe4337MultiSendUserOpCallData, k as bindProductTelemetryInvocation, ki as toEmail, kn as OPERATIONS, kt as normalizeSafeSaltEmail, l as makeSmartAccountMethods, li as sanitizeObservationContext, lr as toExactHex, lt as ZODIAC_MODULE_PROXY_FACTORY, m as PonderIndexerAdapter, mn as toTesterKind, mt as OPERATOR, n as bootIdentityFlow, nn as causeChain, nr as CurrentPolicySchema, oi as attachInvocationObservation, on as openfortSignerNotReadyCode, or as toAccountId, ot as moneySafeOpDigest, p as makeHoldingsMethods, pi as toAllowedOrigin, pn as toKycTier, pt as MULTI_SEND_SELECTOR, qn as OrganizationSetupSchema, qr as CAPXUL_ERROR_CODES, r as makeMeMethods, ri as BetterAuthNodeLayer, rr as diagnostics, s as makeActivityMethods, si as copyInvocationObservation, sn as recoverRawDigestSigner, st as ORG_BUDGET_ACTIONS, t as executeIdentityProductObservation, ti as ClockPortTag, tn as UNOBSERVABLE_SIGNER_STATUS, tr as decodePermissionIntent$1, u as ConvexSmartAccountLayer, ui as redactSecrets, un as makeCurrentUserMethods, ur as toLowerAddress, ut as resolveOrgPermissionIdentity, v as accountPositionsFromWire, vi as AuthClientPortTag, vn as validateHandle, vr as toPermissionAssignmentId, vt as ROLES_SET_ALLOWANCE_ABI, wt as SEND_SIGNATURE, xn as CLI_CAPABILITIES, xr as ChainIdSchema, xt as ROLE_MANAGEMENT_SIGNATURES, yi as detectAuthCacheAdapter, yr as toPermissionId, yt as ROLES_SET_TRANSACTION_UNWRAPPER_ABI, z as logDroppedEvent, zr as observationOnlyControls, zt as failureDetail } from "./access-BnlgphFT.mjs";
|
|
2
2
|
import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, FiberSet, Layer, Logger, Metric, Option, Predicate, References, Result, Schedule, Schema, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
|
|
3
3
|
import { getFunctionName } from "convex/server";
|
|
4
4
|
import { BaseError, decodeFunctionData, encodeAbiParameters, encodeFunctionData, formatUnits, getContractAddress, keccak256, padHex, parseAbiParameters, stringToHex, toBytes, toFunctionSelector, toHex } from "viem";
|
|
@@ -76,798 +76,6 @@ function isPostHogUrlProperty(key) {
|
|
|
76
76
|
return normalized.includes("url") || normalized.includes("referrer") || normalized.includes("path") || normalized.includes("filename");
|
|
77
77
|
}
|
|
78
78
|
//#endregion
|
|
79
|
-
//#region src/activity/documents.ts
|
|
80
|
-
/** A reply its row does not admit is refused without the document body its decode error would carry. */
|
|
81
|
-
function withoutDocumentBody(error) {
|
|
82
|
-
return error.details?.step === "decodeOutput" ? Errors.invalidInput("documentHash", "invalid document read") : error;
|
|
83
|
-
}
|
|
84
|
-
/** Schema errors must not retain document bodies. */
|
|
85
|
-
function decodeDocumentRead(schema, input) {
|
|
86
|
-
return Effect.fromOption(Schema.decodeUnknownOption(schema)(input)).pipe(Effect.mapError(() => ({ publicError: Errors.invalidInput("documentHash", "invalid document read") })));
|
|
87
|
-
}
|
|
88
|
-
function makePaymentDocumentsMethods(deps) {
|
|
89
|
-
/**
|
|
90
|
-
* One document read by its row. The pair is decoded before anything is sent,
|
|
91
|
-
* and a reply the row does not admit is refused without the body it carried.
|
|
92
|
-
*/
|
|
93
|
-
const readDocument = (row, ref, options) => {
|
|
94
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
95
|
-
return runPortEffect(Effect.gen(function* () {
|
|
96
|
-
const pair = yield* decodeDocumentRead(PaymentDocumentReadRefSchema, ref);
|
|
97
|
-
return yield* callOperation(deps.convexCall, row, copyInvocationObservation(controls, pair)).pipe(Effect.mapError((error) => ({ publicError: withoutDocumentBody(error) })));
|
|
98
|
-
}), controls, row.name, deps.runPromise);
|
|
99
|
-
};
|
|
100
|
-
return {
|
|
101
|
-
get: (ref, options) => readDocument(documents.get, ref, options),
|
|
102
|
-
verify: (ref, options) => readDocument(documents.verify, ref, options),
|
|
103
|
-
render: (ref, options) => readDocument(documents.render, ref, options)
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/activity/stitching.ts
|
|
108
|
-
/** Epoch milliseconds of the block that carried the row. */
|
|
109
|
-
function blockMillis(row) {
|
|
110
|
-
return Number(row.timestamp) * 1e3;
|
|
111
|
-
}
|
|
112
|
-
/** `0x1234…abcd` — the counterparty of an unclaimed transfer is an address. */
|
|
113
|
-
function shortAddressLabel(address) {
|
|
114
|
-
return address.length <= 12 ? address : `${address.slice(0, 6)}…${address.slice(-4)}`;
|
|
115
|
-
}
|
|
116
|
-
function directionFor(safe, transfer) {
|
|
117
|
-
const from = transfer.from.toLowerCase();
|
|
118
|
-
const to = transfer.to.toLowerCase();
|
|
119
|
-
if (from === safe && to === safe) return "self";
|
|
120
|
-
return to === safe ? "in" : "out";
|
|
121
|
-
}
|
|
122
|
-
function withAnnotation(item, annotation) {
|
|
123
|
-
return annotation?.counterpartyLabel === void 0 ? item : {
|
|
124
|
-
...item,
|
|
125
|
-
counterpartyLabel: annotation.counterpartyLabel
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* An indexed settlement confirms the intent. A missing row can be outside
|
|
130
|
-
* this page or still backfilling; only the correction owner can prove rollback.
|
|
131
|
-
*/
|
|
132
|
-
function confirmPayment(intent, chain, expected) {
|
|
133
|
-
if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
|
|
134
|
-
...intent,
|
|
135
|
-
status: "settled",
|
|
136
|
-
...intent.execution === void 0 ? {} : { execution: {
|
|
137
|
-
status: "settled",
|
|
138
|
-
retryRequired: false
|
|
139
|
-
} },
|
|
140
|
-
receipt: {
|
|
141
|
-
chainId: chain.chainId,
|
|
142
|
-
transactionHash: chain.txHash
|
|
143
|
-
},
|
|
144
|
-
updatedAt: blockMillis(chain)
|
|
145
|
-
};
|
|
146
|
-
return intent;
|
|
147
|
-
}
|
|
148
|
-
function matchesPaymentFact(chain, expected) {
|
|
149
|
-
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;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
|
|
153
|
-
* denominate its asset. The indexer serves only configured assets, so a row
|
|
154
|
-
* the registry does not know means the SDK and that deployment disagree; it is
|
|
155
|
-
* dropped rather than shown with a guessed currency.
|
|
156
|
-
*/
|
|
157
|
-
function isActivityVisible(item) {
|
|
158
|
-
return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
|
|
159
|
-
}
|
|
160
|
-
function transferItem(safe, transfer, annotation) {
|
|
161
|
-
const asset = configuredMoneyAssetById(transfer.assetId);
|
|
162
|
-
if (asset === null) return null;
|
|
163
|
-
const direction = directionFor(safe.toLowerCase(), transfer);
|
|
164
|
-
const counterparty = direction === "in" ? transfer.from : transfer.to;
|
|
165
|
-
const at = blockMillis(transfer);
|
|
166
|
-
return withAnnotation({
|
|
167
|
-
kind: "movement",
|
|
168
|
-
id: transfer.id,
|
|
169
|
-
classification: "movement_only_outflow",
|
|
170
|
-
amount: {
|
|
171
|
-
asset: asset.assetId,
|
|
172
|
-
value: formatUnits(transfer.amount, asset.decimals)
|
|
173
|
-
},
|
|
174
|
-
direction,
|
|
175
|
-
counterpartyLabel: shortAddressLabel(counterparty),
|
|
176
|
-
receipt: {
|
|
177
|
-
chainId: transfer.chainId,
|
|
178
|
-
transactionHash: transfer.txHash
|
|
179
|
-
},
|
|
180
|
-
createdAt: at,
|
|
181
|
-
updatedAt: at
|
|
182
|
-
}, annotation);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Money in, money out and the pending share, one bucket per exact asset.
|
|
186
|
-
*/
|
|
187
|
-
function summarizeItems(items) {
|
|
188
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
189
|
-
for (const item of items) {
|
|
190
|
-
const metadata = configuredMoneyAssetById(item.amount.asset);
|
|
191
|
-
if (metadata === null) continue;
|
|
192
|
-
const key = item.amount.asset;
|
|
193
|
-
const bucket = buckets.get(key) ?? {
|
|
194
|
-
asset: item.amount.asset,
|
|
195
|
-
in: 0n,
|
|
196
|
-
out: 0n,
|
|
197
|
-
pendingIn: 0n
|
|
198
|
-
};
|
|
199
|
-
const raw = rawUnits(item.amount.value, metadata.decimals);
|
|
200
|
-
if (item.kind === "payment" && item.status !== "settled") {
|
|
201
|
-
if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
|
|
202
|
-
} else if (item.direction === "in") bucket.in += raw;
|
|
203
|
-
else if (item.direction === "out") bucket.out += raw;
|
|
204
|
-
buckets.set(key, bucket);
|
|
205
|
-
}
|
|
206
|
-
return [...buckets.values()].map((bucket) => ({
|
|
207
|
-
asset: bucket.asset,
|
|
208
|
-
in: bucket.in.toString(),
|
|
209
|
-
out: bucket.out.toString(),
|
|
210
|
-
net: (bucket.in - bucket.out).toString(),
|
|
211
|
-
pendingIn: bucket.pendingIn.toString()
|
|
212
|
-
}));
|
|
213
|
-
}
|
|
214
|
-
/** The inverse of `formatUnits`, kept exact: `"1.25"` at 6 decimals is `1250000n`. */
|
|
215
|
-
function rawUnits(value, decimals) {
|
|
216
|
-
const negative = value.startsWith("-");
|
|
217
|
-
const [whole = "0", fraction = ""] = (negative ? value.slice(1) : value).split(".");
|
|
218
|
-
const raw = BigInt(`${whole}${fraction.padEnd(decimals, "0").slice(0, decimals)}`);
|
|
219
|
-
return negative ? -raw : raw;
|
|
220
|
-
}
|
|
221
|
-
//#endregion
|
|
222
|
-
//#region src/activity/cursor.ts
|
|
223
|
-
function actorKey(actor) {
|
|
224
|
-
return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
|
|
225
|
-
}
|
|
226
|
-
function scopeKey(scope) {
|
|
227
|
-
return `${scope.principalAccountId}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
|
|
228
|
-
}
|
|
229
|
-
function selectionKey(params) {
|
|
230
|
-
const filter = params?.filter;
|
|
231
|
-
return JSON.stringify([
|
|
232
|
-
params?.range?.from ?? null,
|
|
233
|
-
params?.range?.to ?? null,
|
|
234
|
-
filter?.kind ?? null,
|
|
235
|
-
filter?.direction ?? null,
|
|
236
|
-
filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
|
|
237
|
-
]);
|
|
238
|
-
}
|
|
239
|
-
function validString(value, max) {
|
|
240
|
-
return typeof value === "string" && value.length <= max;
|
|
241
|
-
}
|
|
242
|
-
function validPosition(position) {
|
|
243
|
-
return position === null || typeof position === "object" && "timestamp" in position && typeof position.timestamp === "number" && Number.isSafeInteger(position.timestamp) && position.timestamp >= 0 && "id" in position && typeof position.id === "string" && /^[1-9][0-9]{0,9}:0x[0-9a-f]{64}:[0-9]{1,10}$/.test(position.id);
|
|
244
|
-
}
|
|
245
|
-
function isActivityCursor(cursor, scope, selection) {
|
|
246
|
-
return typeof cursor === "object" && cursor !== null && cursorScopeMatches(cursor, scope, selection) && cursorProgressIsValid(cursor);
|
|
247
|
-
}
|
|
248
|
-
/** The cursor was issued for this scope and filter selection, at a valid checkpoint. */
|
|
249
|
-
function cursorScopeMatches(cursor, scope, selection) {
|
|
250
|
-
return "version" in cursor && cursor.version === 1 && "scope" in cursor && cursor.scope === scope && "selection" in cursor && cursor.selection === selection && "checkpoint" in cursor && typeof cursor.checkpoint === "number" && Number.isSafeInteger(cursor.checkpoint) && cursor.checkpoint >= 0 && "source" in cursor && (cursor.source === "indexed" || cursor.source === "fallback");
|
|
251
|
-
}
|
|
252
|
-
/** Each source's progress is well-formed and bounded. */
|
|
253
|
-
function cursorProgressIsValid(cursor) {
|
|
254
|
-
return "paymentCursor" in cursor && (cursor.paymentCursor === null || validString(cursor.paymentCursor, 2048)) && "paymentDone" in cursor && typeof cursor.paymentDone === "boolean" && "transferDone" in cursor && typeof cursor.transferDone === "boolean" && "degraded" in cursor && typeof cursor.degraded === "boolean" && "paymentPending" in cursor && Array.isArray(cursor.paymentPending) && cursor.paymentPending.length <= 100 && new Set(cursor.paymentPending).size === cursor.paymentPending.length && cursor.paymentPending.every((id) => validString(id, 256)) && "transferBefore" in cursor && validPosition(cursor.transferBefore);
|
|
255
|
-
}
|
|
256
|
-
function parseCursor(value, scope, selection) {
|
|
257
|
-
if (value === void 0) return null;
|
|
258
|
-
if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
|
|
259
|
-
const cursor = JSON.parse(value);
|
|
260
|
-
if (!isActivityCursor(cursor, scope, selection)) throw Errors.invalidInput("cursor", "Activity scope or filters changed; restart from the first page");
|
|
261
|
-
return cursor;
|
|
262
|
-
}
|
|
263
|
-
function requireParams(params) {
|
|
264
|
-
const limit = params?.limit ?? 25;
|
|
265
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
|
|
266
|
-
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");
|
|
267
|
-
const f = params?.filter;
|
|
268
|
-
if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
|
|
269
|
-
"in",
|
|
270
|
-
"out",
|
|
271
|
-
"self"
|
|
272
|
-
].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");
|
|
273
|
-
}
|
|
274
|
-
function matchesFilter(item, filter) {
|
|
275
|
-
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));
|
|
276
|
-
}
|
|
277
|
-
//#endregion
|
|
278
|
-
//#region src/activity/indexed-pages.ts
|
|
279
|
-
function chainRead(deps, table, input) {
|
|
280
|
-
return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
|
|
281
|
-
if (error.publicError.details?.reason === "scope-refused" || ![
|
|
282
|
-
"NETWORK_ERROR",
|
|
283
|
-
"RATE_LIMITED",
|
|
284
|
-
"PROVIDER_ERROR",
|
|
285
|
-
"CAPABILITY_UNAVAILABLE",
|
|
286
|
-
"TIMEOUT"
|
|
287
|
-
].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
|
|
288
|
-
return Effect.succeed(null);
|
|
289
|
-
}));
|
|
290
|
-
}
|
|
291
|
-
function boundedRange(range, checkpoint) {
|
|
292
|
-
return {
|
|
293
|
-
...range?.from === void 0 ? {} : { from: range.from },
|
|
294
|
-
to: Math.min(checkpoint, range?.to ?? checkpoint)
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
function unavailablePage(deps, scope, controls, params, state, first) {
|
|
298
|
-
if (first) return fallbackPage(deps, scope, controls, params, state, true);
|
|
299
|
-
return Effect.annotateCurrentSpan({
|
|
300
|
-
read_source: "indexer",
|
|
301
|
-
read_warning: "indexer-unavailable"
|
|
302
|
-
}).pipe(Effect.as({
|
|
303
|
-
items: [],
|
|
304
|
-
nextCursor: null,
|
|
305
|
-
checkpoint: state.checkpoint,
|
|
306
|
-
observedAt: Date.now(),
|
|
307
|
-
readWarning: "indexer-unavailable"
|
|
308
|
-
}));
|
|
309
|
-
}
|
|
310
|
-
function readTransfers(deps, safe, range, state, wanted, first) {
|
|
311
|
-
const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
|
|
312
|
-
const to = Math.floor(range.to / 1e3);
|
|
313
|
-
if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
|
|
314
|
-
return chainRead(deps, "transfer", {
|
|
315
|
-
safe: toAddress(safe),
|
|
316
|
-
limit: wanted ? 100 : 1,
|
|
317
|
-
timeline: {
|
|
318
|
-
from,
|
|
319
|
-
to,
|
|
320
|
-
...state.transferBefore === null ? {} : { before: state.transferBefore }
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
function readPayments(deps, scope, controls, range, state, wanted) {
|
|
325
|
-
const done = state.paymentDone || !wanted;
|
|
326
|
-
if (state.paymentPending.length > 0 || done) return Effect.succeed({
|
|
327
|
-
items: [],
|
|
328
|
-
pending: state.paymentPending,
|
|
329
|
-
cursor: state.paymentCursor,
|
|
330
|
-
done
|
|
331
|
-
});
|
|
332
|
-
return convexPage(deps, scope.actor, controls, {
|
|
333
|
-
limit: 100,
|
|
334
|
-
range,
|
|
335
|
-
filter: { kind: "payment" },
|
|
336
|
-
...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
|
|
337
|
-
}).pipe(Effect.map((page) => ({
|
|
338
|
-
items: page.items,
|
|
339
|
-
pending: page.items.map((item) => item.id),
|
|
340
|
-
cursor: page.nextCursor,
|
|
341
|
-
done: page.nextCursor === null
|
|
342
|
-
})));
|
|
343
|
-
}
|
|
344
|
-
function readPaymentFacts(deps, safe, settlements) {
|
|
345
|
-
const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
|
|
346
|
-
return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
|
|
347
|
-
safe: toAddress(safe),
|
|
348
|
-
limit: 50,
|
|
349
|
-
settlements: settlements.slice(offset, offset + 50)
|
|
350
|
-
}), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
|
|
351
|
-
}
|
|
352
|
-
function indexedPage(deps, scope, controls, params, state, first) {
|
|
353
|
-
return Effect.gen(function* () {
|
|
354
|
-
const range = boundedRange(params?.range, state.checkpoint);
|
|
355
|
-
const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
|
|
356
|
-
const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
|
|
357
|
-
if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
|
|
358
|
-
const transfers = wantsTransfers ? found : [];
|
|
359
|
-
const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
|
|
360
|
-
const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
|
|
361
|
-
if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
|
|
362
|
-
const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
|
|
363
|
-
const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
|
|
364
|
-
if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
|
|
365
|
-
yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
|
|
366
|
-
return {
|
|
367
|
-
items,
|
|
368
|
-
nextCursor: cursor,
|
|
369
|
-
checkpoint: state.checkpoint,
|
|
370
|
-
observedAt: Date.now()
|
|
371
|
-
};
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
//#endregion
|
|
375
|
-
//#region src/activity/page-overlay.ts
|
|
376
|
-
const MAX_OVERLAY_IDS = 200;
|
|
377
|
-
function overlay(deps, actor, controls, input) {
|
|
378
|
-
const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
|
|
379
|
-
const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
|
|
380
|
-
return Effect.forEach(offsets, (offset) => callOperation(deps.convexCall, overlays.get, copyInvocationObservation(controls, {
|
|
381
|
-
...actor === void 0 ? {} : { actor },
|
|
382
|
-
...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
|
|
383
|
-
...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
|
|
384
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError }))), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
|
|
385
|
-
const first = pages[0];
|
|
386
|
-
if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
|
|
387
|
-
return Effect.succeed({
|
|
388
|
-
...first,
|
|
389
|
-
links: pages.flatMap((page) => page.links),
|
|
390
|
-
annotations: pages.flatMap((page) => page.annotations),
|
|
391
|
-
payments: pages.flatMap((page) => page.payments)
|
|
392
|
-
});
|
|
393
|
-
}));
|
|
394
|
-
}
|
|
395
|
-
function decoratePage(deps, scope, controls, transfers, page, range) {
|
|
396
|
-
return Effect.gen(function* () {
|
|
397
|
-
const decoration = yield* overlay(deps, scope.actor, controls, {
|
|
398
|
-
references: [...page.pending.map((id) => ({
|
|
399
|
-
kind: "payment",
|
|
400
|
-
id
|
|
401
|
-
})), ...transfers.map((row) => ({
|
|
402
|
-
kind: "movement",
|
|
403
|
-
id: row.id
|
|
404
|
-
}))],
|
|
405
|
-
settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
|
|
406
|
-
});
|
|
407
|
-
if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
|
|
408
|
-
const byId = new Map([...page.items, ...decoration.payments].map((item) => [item.id, item]));
|
|
409
|
-
const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
|
|
410
|
-
const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
|
|
411
|
-
if (facts === null) return null;
|
|
412
|
-
const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
|
|
413
|
-
const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
|
|
414
|
-
fact: bySettlement.get(link.settlementId),
|
|
415
|
-
expected: link.expected
|
|
416
|
-
}]));
|
|
417
|
-
const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
|
|
418
|
-
return {
|
|
419
|
-
payments: page.pending.flatMap((id) => {
|
|
420
|
-
const item = byId.get(id);
|
|
421
|
-
if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
|
|
422
|
-
const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
|
|
423
|
-
return isActivityVisible(confirmed) ? [confirmed] : [];
|
|
424
|
-
}),
|
|
425
|
-
annotations,
|
|
426
|
-
linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
|
|
427
|
-
};
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
|
|
431
|
-
const { payments, annotations, linked } = decoration;
|
|
432
|
-
let pi = 0;
|
|
433
|
-
let ti = 0;
|
|
434
|
-
const items = [];
|
|
435
|
-
const transferEnd = !wantsTransfers || transfers.length < 100;
|
|
436
|
-
let transferBefore = state.transferBefore;
|
|
437
|
-
while (items.length < limit) {
|
|
438
|
-
if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
|
|
439
|
-
const payment = payments[pi];
|
|
440
|
-
const row = transfers[ti];
|
|
441
|
-
if (payment === void 0 && row === void 0) break;
|
|
442
|
-
if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
|
|
443
|
-
pi += 1;
|
|
444
|
-
if (matchesFilter(payment, filter)) items.push(payment);
|
|
445
|
-
} else if (row !== void 0) {
|
|
446
|
-
ti += 1;
|
|
447
|
-
transferBefore = {
|
|
448
|
-
timestamp: Number(row.timestamp),
|
|
449
|
-
id: row.id
|
|
450
|
-
};
|
|
451
|
-
if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
|
|
452
|
-
const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
|
|
453
|
-
if (item !== null && matchesFilter(item, filter)) items.push(item);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return {
|
|
457
|
-
items,
|
|
458
|
-
next: {
|
|
459
|
-
...state,
|
|
460
|
-
paymentCursor: page.cursor,
|
|
461
|
-
paymentDone: page.done,
|
|
462
|
-
paymentPending: payments.slice(pi).map((item) => item.id),
|
|
463
|
-
transferBefore,
|
|
464
|
-
transferDone: ti === transfers.length && transferEnd
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
function convexPage(deps, actor, controls, input) {
|
|
469
|
-
return callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
|
|
470
|
-
...input,
|
|
471
|
-
actor
|
|
472
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError })));
|
|
473
|
-
}
|
|
474
|
-
function fallbackPage(deps, scope, controls, params, state, degraded) {
|
|
475
|
-
return Effect.gen(function* () {
|
|
476
|
-
const page = yield* convexPage(deps, scope.actor, controls, {
|
|
477
|
-
limit: params?.limit ?? 25,
|
|
478
|
-
...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
|
|
479
|
-
range: {
|
|
480
|
-
...params?.range?.from === void 0 ? {} : { from: params.range.from },
|
|
481
|
-
to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
|
|
482
|
-
},
|
|
483
|
-
...params?.filter === void 0 ? {} : { filter: params.filter }
|
|
484
|
-
});
|
|
485
|
-
const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
|
|
486
|
-
kind: item.kind,
|
|
487
|
-
id: item.id
|
|
488
|
-
})) });
|
|
489
|
-
if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
|
|
490
|
-
const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
|
|
491
|
-
yield* Effect.annotateCurrentSpan({
|
|
492
|
-
read_source: "convex",
|
|
493
|
-
...degraded ? { read_warning: "indexer-unavailable" } : {}
|
|
494
|
-
});
|
|
495
|
-
return {
|
|
496
|
-
...page,
|
|
497
|
-
items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
|
|
498
|
-
checkpoint: state.checkpoint,
|
|
499
|
-
nextCursor: page.nextCursor === null ? null : JSON.stringify({
|
|
500
|
-
...state,
|
|
501
|
-
source: "fallback",
|
|
502
|
-
paymentCursor: page.nextCursor,
|
|
503
|
-
degraded
|
|
504
|
-
}),
|
|
505
|
-
...degraded ? { readWarning: "indexer-unavailable" } : {}
|
|
506
|
-
};
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
function readActivityPage(deps, actor, controls, params) {
|
|
510
|
-
return Effect.gen(function* () {
|
|
511
|
-
yield* Effect.try({
|
|
512
|
-
try: () => requireParams(params),
|
|
513
|
-
catch: (cause) => ({ publicError: asCapxulError(cause) })
|
|
514
|
-
});
|
|
515
|
-
const scope = yield* overlay(deps, actor, controls, {});
|
|
516
|
-
const selection = selectionKey(params);
|
|
517
|
-
const cursor = yield* Effect.try({
|
|
518
|
-
try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
|
|
519
|
-
catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
|
|
520
|
-
});
|
|
521
|
-
const state = cursor ?? {
|
|
522
|
-
version: 1,
|
|
523
|
-
scope: scopeKey(scope),
|
|
524
|
-
selection,
|
|
525
|
-
checkpoint: Date.now(),
|
|
526
|
-
source: "indexed",
|
|
527
|
-
paymentCursor: null,
|
|
528
|
-
paymentDone: false,
|
|
529
|
-
paymentPending: [],
|
|
530
|
-
transferBefore: null,
|
|
531
|
-
transferDone: false,
|
|
532
|
-
degraded: false
|
|
533
|
-
};
|
|
534
|
-
const range = boundedRange(params?.range, state.checkpoint);
|
|
535
|
-
if (range.from !== void 0 && range.from > range.to) return {
|
|
536
|
-
items: [],
|
|
537
|
-
nextCursor: null,
|
|
538
|
-
checkpoint: state.checkpoint,
|
|
539
|
-
observedAt: Date.now()
|
|
540
|
-
};
|
|
541
|
-
if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
|
|
542
|
-
return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
function readActivitySummary(deps, actor, controls, params) {
|
|
546
|
-
return Effect.gen(function* () {
|
|
547
|
-
const totals = /* @__PURE__ */ new Map();
|
|
548
|
-
let cursor;
|
|
549
|
-
let count = 0;
|
|
550
|
-
let readWarning;
|
|
551
|
-
do {
|
|
552
|
-
const page = yield* readActivityPage(deps, actor, controls, {
|
|
553
|
-
limit: 100,
|
|
554
|
-
...cursor === void 0 ? {} : { cursor },
|
|
555
|
-
...params?.window === void 0 ? {} : { range: params.window }
|
|
556
|
-
});
|
|
557
|
-
count += page.items.length;
|
|
558
|
-
readWarning ??= page.readWarning;
|
|
559
|
-
for (const total of summarizeItems(page.items)) {
|
|
560
|
-
const key = total.asset;
|
|
561
|
-
const old = totals.get(key);
|
|
562
|
-
totals.set(key, old === void 0 ? total : {
|
|
563
|
-
...total,
|
|
564
|
-
in: String(BigInt(old.in) + BigInt(total.in)),
|
|
565
|
-
out: String(BigInt(old.out) + BigInt(total.out)),
|
|
566
|
-
net: String(BigInt(old.net) + BigInt(total.net)),
|
|
567
|
-
pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
cursor = page.nextCursor ?? void 0;
|
|
571
|
-
} while (cursor !== void 0);
|
|
572
|
-
return {
|
|
573
|
-
window: {
|
|
574
|
-
from: params?.window?.from ?? null,
|
|
575
|
-
to: params?.window?.to ?? null
|
|
576
|
-
},
|
|
577
|
-
totals: [...totals.values()],
|
|
578
|
-
count,
|
|
579
|
-
observedAt: Date.now(),
|
|
580
|
-
...readWarning === void 0 ? {} : { readWarning }
|
|
581
|
-
};
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
function subscribeActivity(deps, actor, controls, onChange) {
|
|
585
|
-
return Effect.gen(function* () {
|
|
586
|
-
const scope = yield* overlay(deps, actor, controls, {});
|
|
587
|
-
if (scope.safe === null) return () => {};
|
|
588
|
-
let delivered = false;
|
|
589
|
-
return yield* deps.indexer.subscribe("transfer", {
|
|
590
|
-
safe: toAddress(scope.safe),
|
|
591
|
-
limit: 100
|
|
592
|
-
}, (snapshot) => {
|
|
593
|
-
if (snapshot.status === "ok") {
|
|
594
|
-
if (delivered) onChange();
|
|
595
|
-
delivered = true;
|
|
596
|
-
}
|
|
597
|
-
if (snapshot.status === "error") {
|
|
598
|
-
delivered = true;
|
|
599
|
-
onChange();
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
//#endregion
|
|
605
|
-
//#region src/activity/movement-detail.ts
|
|
606
|
-
/** An indexed Movement has no Convex materialization. Read its exact native row. */
|
|
607
|
-
function readIndexedMovement(deps, actor, controls, eventId) {
|
|
608
|
-
return Effect.gen(function* () {
|
|
609
|
-
const scope = yield* overlay(deps, actor, controls, { references: [{
|
|
610
|
-
kind: "movement",
|
|
611
|
-
id: eventId
|
|
612
|
-
}] });
|
|
613
|
-
if (scope.safe === null) return null;
|
|
614
|
-
const safe = scope.safe.toLowerCase();
|
|
615
|
-
const row = (yield* deps.indexer.read("transfer", {
|
|
616
|
-
safe: toAddress(safe),
|
|
617
|
-
limit: 1,
|
|
618
|
-
eventId
|
|
619
|
-
}).pipe(Effect.mapError((error) => ({ publicError: error.publicError }))))[0];
|
|
620
|
-
if (row === void 0) return null;
|
|
621
|
-
const blockNumber = Number(row.blockNumber);
|
|
622
|
-
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") });
|
|
623
|
-
yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
|
|
624
|
-
const documentSet = scope.movementDocuments.find((set) => set.movementId === row.id);
|
|
625
|
-
return {
|
|
626
|
-
kind: "movement",
|
|
627
|
-
movementId: row.id,
|
|
628
|
-
documents: documentSet?.documents ?? [],
|
|
629
|
-
documentState: documentSet?.state ?? "pending",
|
|
630
|
-
viewer: scope.actor,
|
|
631
|
-
annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
|
|
632
|
-
evidence: {
|
|
633
|
-
chainId: row.chainId,
|
|
634
|
-
contractAddress: row.token,
|
|
635
|
-
tokenAddress: row.token,
|
|
636
|
-
transactionHash: row.txHash,
|
|
637
|
-
blockNumber,
|
|
638
|
-
blockHash: row.blockHash,
|
|
639
|
-
inclusion: "included",
|
|
640
|
-
verification: "verified",
|
|
641
|
-
canonicalState: "finalized",
|
|
642
|
-
transferLogIndex: row.logIndex,
|
|
643
|
-
source: row.from,
|
|
644
|
-
destination: row.to,
|
|
645
|
-
rawAmount: row.amount.toString(),
|
|
646
|
-
link: {
|
|
647
|
-
movementId: row.id,
|
|
648
|
-
depositPaymentId: null
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
};
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
//#endregion
|
|
655
|
-
//#region src/activity/scope.ts
|
|
656
|
-
/**
|
|
657
|
-
* The activity backend gives its actor a different name than the other
|
|
658
|
-
* financialOps functions: `{kind: "organization", orgId}`, and not
|
|
659
|
-
* `{kind: "org", orgId}`. Thus it does not use `actorReferenceToBackend`.
|
|
660
|
-
*
|
|
661
|
-
* The `personal` case sends NO actor. This is correct, and it does not lose
|
|
662
|
-
* data. `requireActivityActor` uses the signed-in Account when the actor is
|
|
663
|
-
* absent, and `canReadActor` refuses all other account identifiers
|
|
664
|
-
* (`packages/backend/convex/movement/activityAuth.ts`). Thus "personal" and
|
|
665
|
-
* "absent" both name the one Account that an SDK caller can read. The SDK does
|
|
666
|
-
* not hold an account identifier to send.
|
|
667
|
-
*
|
|
668
|
-
* The `switch` is exhaustive. A new `ActorReference` variant is a compile error
|
|
669
|
-
* here. The conditional expression that this function replaced removed each
|
|
670
|
-
* value that was not an organization.
|
|
671
|
-
*/
|
|
672
|
-
function activityActorField(actor) {
|
|
673
|
-
if (actor === void 0 || actor.kind === "personal") return {};
|
|
674
|
-
return { actor: {
|
|
675
|
-
kind: "organization",
|
|
676
|
-
orgId: actor.organizationId
|
|
677
|
-
} };
|
|
678
|
-
}
|
|
679
|
-
/** Capture scope now; let the observed result boundary handle a malformed caller value. */
|
|
680
|
-
function captureActivityInput(prepare) {
|
|
681
|
-
try {
|
|
682
|
-
return prepare();
|
|
683
|
-
} catch (cause) {
|
|
684
|
-
return Effect.die(cause);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
function observeActivityScope(actor) {
|
|
688
|
-
const organizationId = actor?.kind === "organization" || actor?.kind === "org" ? safeEngineeringIdentifier(actor.orgId) : void 0;
|
|
689
|
-
return Effect.annotateCurrentSpan({
|
|
690
|
-
scope_kind: actor?.kind === "organization" || actor?.kind === "org" ? "organization" : "account",
|
|
691
|
-
...organizationId === void 0 ? {} : { verified_organization_id: organizationId }
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
function decodeActivityViewer(value) {
|
|
695
|
-
const viewer = Option.getOrUndefined(Schema.decodeUnknownOption(ActorRefSchema)(value));
|
|
696
|
-
if (viewer === void 0) return void 0;
|
|
697
|
-
const id = viewer.kind === "organization" ? viewer.orgId : viewer.accountId;
|
|
698
|
-
return safeEngineeringIdentifier(id) === void 0 ? void 0 : viewer;
|
|
699
|
-
}
|
|
700
|
-
/** The detail without the scope it was read for, which the SDK reports as telemetry instead. */
|
|
701
|
-
function normalizeActivityDetail(response) {
|
|
702
|
-
if (response === null) return null;
|
|
703
|
-
const { viewer: _viewer, ...detail } = response;
|
|
704
|
-
return detail;
|
|
705
|
-
}
|
|
706
|
-
//#endregion
|
|
707
|
-
//#region src/activity/methods.ts
|
|
708
|
-
function makeActivityMethods(deps) {
|
|
709
|
-
const indexerDeps = deps.indexerRead === void 0 ? void 0 : {
|
|
710
|
-
indexer: deps.indexerRead,
|
|
711
|
-
convexCall: deps.convexCall
|
|
712
|
-
};
|
|
713
|
-
/** One public call for the caller's chosen actor, read once at call start. */
|
|
714
|
-
const forActor = (actorRef, options, operation, program) => {
|
|
715
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
716
|
-
return runPortEffect(captureActivityInput(() => Effect.succeed(activityActorField(actorRef).actor)).pipe(Effect.flatMap((actor) => program(actor, controls))), controls, operation, deps.runPromise);
|
|
717
|
-
};
|
|
718
|
-
return {
|
|
719
|
-
subscribe: (onChange, params, options) => forActor(params?.actor, options, CAPXUL_OPERATIONS.activity.subscribe, (actor, controls) => indexerDeps === void 0 ? Effect.succeed(() => {}) : Effect.suspend(() => subscribeActivity(indexerDeps, actor, controls, onChange)).pipe(Effect.tap(() => observeActivityScope(actor)))),
|
|
720
|
-
list: (params, options) => forActor(params?.actor, options, items.list.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.list, copyInvocationObservation(controls, {
|
|
721
|
-
...actor === void 0 ? {} : { actor },
|
|
722
|
-
...params?.cursor === void 0 ? {} : { cursor: params.cursor },
|
|
723
|
-
...params?.limit === void 0 ? {} : { limit: params.limit },
|
|
724
|
-
...params?.range === void 0 ? {} : { range: params.range },
|
|
725
|
-
...params?.filter === void 0 ? {} : { filter: params.filter }
|
|
726
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
|
|
727
|
-
...page,
|
|
728
|
-
items: page.items.filter(isActivityVisible)
|
|
729
|
-
})), Effect.tap(() => observeActivityScope(actor)))),
|
|
730
|
-
summary: (params, options) => forActor(params?.actor, options, items.summary.name, (actor, controls) => Effect.suspend(() => indexerDeps === void 0 ? callOperation(deps.convexCall, items.summary, copyInvocationObservation(controls, {
|
|
731
|
-
...actor === void 0 ? {} : { actor },
|
|
732
|
-
...params?.window === void 0 ? {} : { window: params.window }
|
|
733
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError }))) : readActivitySummary(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor)))),
|
|
734
|
-
get: (reference, options) => {
|
|
735
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
736
|
-
const requestedActor = actorReferenceToBackend(options?.actor);
|
|
737
|
-
const capturedScope = captureActivityInput(() => {
|
|
738
|
-
const scope = requestedActor === void 0 ? void 0 : requestedActor.kind === "org" ? {
|
|
739
|
-
kind: "org",
|
|
740
|
-
orgId: requestedActor.orgId
|
|
741
|
-
} : { kind: "account" };
|
|
742
|
-
return Effect.succeed({
|
|
743
|
-
actor: scope,
|
|
744
|
-
referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
|
|
745
|
-
});
|
|
746
|
-
});
|
|
747
|
-
return runPortEffect(capturedScope.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" ? {
|
|
748
|
-
kind: "organization",
|
|
749
|
-
orgId: actor.orgId
|
|
750
|
-
} : void 0, controls, reference.id) : callOperation(deps.convexCall, items.get, copyInvocationObservation(controls, {
|
|
751
|
-
reference: {
|
|
752
|
-
kind: reference.kind,
|
|
753
|
-
id: reference.id,
|
|
754
|
-
...referenceActor === void 0 ? {} : { actor: referenceActor }
|
|
755
|
-
},
|
|
756
|
-
...actor === void 0 ? {} : { actor }
|
|
757
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.flatMap((response) => {
|
|
758
|
-
const detail = normalizeActivityDetail(response);
|
|
759
|
-
if (response === null) return Effect.succeed(detail);
|
|
760
|
-
const viewer = response.viewer === void 0 ? actor ?? referenceActor : decodeActivityViewer(response.viewer);
|
|
761
|
-
return viewer === void 0 ? Effect.succeed(detail) : observeActivityScope(viewer).pipe(Effect.as(detail));
|
|
762
|
-
})))), controls, items.get.name, deps.runPromise);
|
|
763
|
-
},
|
|
764
|
-
annotate: (input, options) => {
|
|
765
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
766
|
-
const capturedScope = captureActivityInput(() => {
|
|
767
|
-
const actor = activityActorField(input.actor).actor;
|
|
768
|
-
if (input.actor !== void 0 && input.reference.kind === "payment" && input.reference.actor !== void 0 && !sameActor(input.actor, input.reference.actor)) return Effect.fail({ publicError: Errors.invalidInput("reference.actor", "must match the annotation actor") });
|
|
769
|
-
return Effect.succeed({
|
|
770
|
-
actor,
|
|
771
|
-
referenceActor: input.reference.kind === "payment" ? activityActorField(input.reference.actor).actor : void 0
|
|
772
|
-
});
|
|
773
|
-
});
|
|
774
|
-
return runPortEffect(capturedScope.pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => callOperation(deps.convexCall, annotations.save, copyInvocationObservation(controls, {
|
|
775
|
-
...actor === void 0 ? {} : { actor },
|
|
776
|
-
reference: input.reference.kind === "payment" && referenceActor !== void 0 ? {
|
|
777
|
-
kind: "payment",
|
|
778
|
-
id: input.reference.id,
|
|
779
|
-
actor: referenceActor
|
|
780
|
-
} : {
|
|
781
|
-
kind: input.reference.kind,
|
|
782
|
-
id: input.reference.id
|
|
783
|
-
},
|
|
784
|
-
...input.counterpartyLabel === void 0 ? {} : { counterpartyLabel: input.counterpartyLabel },
|
|
785
|
-
...input.accountingCategory === void 0 ? {} : { accountingCategory: input.accountingCategory },
|
|
786
|
-
...input.memo === void 0 ? {} : { memo: input.memo }
|
|
787
|
-
})).pipe(Effect.mapError((publicError) => ({ publicError })))).pipe(Effect.tap((annotation) => {
|
|
788
|
-
const scope = decodeActivityViewer(annotation.actor);
|
|
789
|
-
return scope === void 0 ? Effect.void : observeActivityScope(scope);
|
|
790
|
-
})))), controls, annotations.save.name, deps.runPromise);
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
//#endregion
|
|
795
|
-
//#region src/activity/phases.ts
|
|
796
|
-
const PENDING = Object.freeze({
|
|
797
|
-
terminal: false,
|
|
798
|
-
outcome: null,
|
|
799
|
-
needsAction: false
|
|
800
|
-
});
|
|
801
|
-
const AWAITING_ACTOR = Object.freeze({
|
|
802
|
-
terminal: false,
|
|
803
|
-
outcome: null,
|
|
804
|
-
needsAction: true
|
|
805
|
-
});
|
|
806
|
-
const COMPLETED = Object.freeze({
|
|
807
|
-
terminal: true,
|
|
808
|
-
outcome: "good",
|
|
809
|
-
needsAction: false
|
|
810
|
-
});
|
|
811
|
-
const FAILED = Object.freeze({
|
|
812
|
-
terminal: true,
|
|
813
|
-
outcome: "bad",
|
|
814
|
-
needsAction: false
|
|
815
|
-
});
|
|
816
|
-
const WITHDRAWN = Object.freeze({
|
|
817
|
-
terminal: true,
|
|
818
|
-
outcome: "neutral",
|
|
819
|
-
needsAction: false
|
|
820
|
-
});
|
|
821
|
-
/** Interpret a Payment status. `pending_claim` waits for the actor who reads it. */
|
|
822
|
-
function paymentPhase(status) {
|
|
823
|
-
switch (status) {
|
|
824
|
-
case "pending":
|
|
825
|
-
case "settling":
|
|
826
|
-
case "scheduled":
|
|
827
|
-
case "streaming": return PENDING;
|
|
828
|
-
case "pending_claim": return AWAITING_ACTOR;
|
|
829
|
-
case "settled": return COMPLETED;
|
|
830
|
-
case "cancelled": return WITHDRAWN;
|
|
831
|
-
case "failed":
|
|
832
|
-
case "unresolved": return FAILED;
|
|
833
|
-
default: return absurd(status);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
/** Interpret a payment Request as its ISSUER sees it. An issuer does not wait for itself. */
|
|
837
|
-
function requestPhase(status) {
|
|
838
|
-
switch (status) {
|
|
839
|
-
case "draft":
|
|
840
|
-
case "sent":
|
|
841
|
-
case "viewed":
|
|
842
|
-
case "pending_settlement": return PENDING;
|
|
843
|
-
case "paid": return COMPLETED;
|
|
844
|
-
case "declined":
|
|
845
|
-
case "expired": return FAILED;
|
|
846
|
-
case "cancelled": return WITHDRAWN;
|
|
847
|
-
default: return absurd(status);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
/** Interpret the same request as its RECIPIENT sees it. `open` waits for the recipient. */
|
|
851
|
-
function inboxPhase(status) {
|
|
852
|
-
switch (status) {
|
|
853
|
-
case "open": return AWAITING_ACTOR;
|
|
854
|
-
case "approved": return PENDING;
|
|
855
|
-
case "paid": return COMPLETED;
|
|
856
|
-
case "declined":
|
|
857
|
-
case "expired": return FAILED;
|
|
858
|
-
case "cancelled": return WITHDRAWN;
|
|
859
|
-
default: return absurd(status);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
//#endregion
|
|
863
|
-
//#region ../protocol/src/payroll/model.ts
|
|
864
|
-
function toPayrollRunId(raw) {
|
|
865
|
-
return toNonEmptyStringBrand(raw, "payrollRunId", (value) => isNonEmptyString(value));
|
|
866
|
-
}
|
|
867
|
-
function toPayrollGroupId(raw) {
|
|
868
|
-
return toNonEmptyStringBrand(raw, "payrollGroupId", (value) => isNonEmptyString(value));
|
|
869
|
-
}
|
|
870
|
-
//#endregion
|
|
871
79
|
//#region src/permissions/command-decoding.ts
|
|
872
80
|
/** The caller's input as a command: ids branded and the intent decoded, or invalid input thrown. */
|
|
873
81
|
const buildCommand = {
|
|
@@ -12016,411 +11224,6 @@ function makePermissionMethods(deps, orgId) {
|
|
|
12016
11224
|
};
|
|
12017
11225
|
}
|
|
12018
11226
|
//#endregion
|
|
12019
|
-
//#region src/payroll/run-checks.ts
|
|
12020
|
-
/** Match the caller's compensation to the committed V3 Payslip. */
|
|
12021
|
-
function payslipIdentityMatches(message, item, index, runId, period) {
|
|
12022
|
-
return message.runId === runId && message.itemIndex === index && message.employeeRef === item.partyId && message.periodStart === period.start && message.periodEnd === period.end && message.adjustmentsHash === hashAdjustmentsV3(item.adjustments);
|
|
12023
|
-
}
|
|
12024
|
-
function payslipAmountMatches(message, item) {
|
|
12025
|
-
const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
|
|
12026
|
-
return message.gross.asset === exact.asset && message.gross.minorUnits === item.gross && message.gross.decimals === exact.decimals && message.net.asset === exact.asset && message.net.minorUnits === item.net && message.net.decimals === exact.decimals;
|
|
12027
|
-
}
|
|
12028
|
-
function isRunItem(prepared, item, evidence, index, runId, period) {
|
|
12029
|
-
if (prepared === void 0) return false;
|
|
12030
|
-
const bundle = evidence.preparedCommand?.documentBundles?.[index];
|
|
12031
|
-
const preparedItem = evidence.preparedCommand?.items[index];
|
|
12032
|
-
const payslip = bundle?.envelope;
|
|
12033
|
-
if (bundle === void 0 || payslip?.primaryType !== "Payslip") return false;
|
|
12034
|
-
return canonicalJson(prepared.to) === canonicalJson(item.to) && prepared.paymentType === "payroll" && canonicalJson(prepared.amount) === canonicalJson(item.amount) && bundle.adjustments !== void 0 && canonicalJson(bundle.adjustments) === canonicalJson(item.adjustments) && payslipIdentityMatches(payslip.message, item, index, runId, period) && payslipAmountMatches(payslip.message, item) && (preparedItem?.operation === "send" || preparedItem?.operation === "create_commitment") && preparedItem.documentHash === evidence.preparedCommand?.instructionDocuments?.[index]?.documentHash;
|
|
12035
|
-
}
|
|
12036
|
-
/**
|
|
12037
|
-
* The batch the backend derived from a payroll intent must be the batch this
|
|
12038
|
-
* caller asked for, checked before the digest is signed.
|
|
12039
|
-
*
|
|
12040
|
-
* The payslip envelope on each item is server-built and carries the run row's
|
|
12041
|
-
* own `issuedAt`, so the batch cannot be fingerprinted against a local copy
|
|
12042
|
-
* (`matchesIntent` is unavailable here). What CAN be verified is every field
|
|
12043
|
-
* the caller supplied — the org, the Budget, the request key, and per item the
|
|
12044
|
-
* recipient, the amount and the payslip figures — plus the lineage binding the
|
|
12045
|
-
* batch to the run just written. A prepared command that pays a different
|
|
12046
|
-
* person, a different amount, from a different Budget, or under a different
|
|
12047
|
-
* run never reaches the signer.
|
|
12048
|
-
*/
|
|
12049
|
-
function isRunBatch(input) {
|
|
12050
|
-
return (batch, evidence) => Promise.resolve(!("kind" in batch) && batch.orgId === input.orgId && batch.permissionId === input.permissionId && batch.requestKey === input.requestKey && batch.lineage?.kind === "payroll" && batch.lineage.runId === input.runId && batch.items.length === input.items.length && batch.items.every((item) => item.amount.asset === input.asset) && input.items.every((item, index) => isRunItem(batch.items[index], item, evidence, index, input.runId, input.period)));
|
|
12051
|
-
}
|
|
12052
|
-
/** A decoded detail is the run asked for, whole: its scope, membership and quantities agree. */
|
|
12053
|
-
function checkPayrollRunDetail(value, orgId, runId) {
|
|
12054
|
-
if (value === null) return {
|
|
12055
|
-
ok: true,
|
|
12056
|
-
value: null
|
|
12057
|
-
};
|
|
12058
|
-
if (value.orgId !== orgId || value.run.id !== runId || value.items.length === 0 || value.items.length !== value.run.recipientCount || new Set(value.items.map((item) => item.payment.id)).size !== value.items.length || value.items.some((item, index) => item.itemIndex !== index || item.amount.asset !== value.run.total.asset || item.payment.amount.asset !== item.amount.asset || item.payment.paymentType !== "payroll")) return {
|
|
12059
|
-
ok: false,
|
|
12060
|
-
error: Errors.invalidInput("run", "Payroll run scope or membership does not match")
|
|
12061
|
-
};
|
|
12062
|
-
try {
|
|
12063
|
-
const total = toExactAssetAmount(value.run.total, CONFIGURED_MONEY_ASSETS);
|
|
12064
|
-
let rawTotal = 0n;
|
|
12065
|
-
for (const item of value.items) {
|
|
12066
|
-
const amount = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
|
|
12067
|
-
const payment = toExactAssetAmount(item.payment.amount, CONFIGURED_MONEY_ASSETS);
|
|
12068
|
-
if (amount.minorUnits !== payment.minorUnits) return {
|
|
12069
|
-
ok: false,
|
|
12070
|
-
error: Errors.invalidInput("run", "Payroll item quantity does not match its Payment")
|
|
12071
|
-
};
|
|
12072
|
-
rawTotal += BigInt(amount.minorUnits);
|
|
12073
|
-
}
|
|
12074
|
-
if (rawTotal !== BigInt(total.minorUnits)) return {
|
|
12075
|
-
ok: false,
|
|
12076
|
-
error: Errors.invalidInput("run", "Payroll run total does not match its items")
|
|
12077
|
-
};
|
|
12078
|
-
} catch {
|
|
12079
|
-
return {
|
|
12080
|
-
ok: false,
|
|
12081
|
-
error: Errors.invalidInput("run", "Payroll run quantities are invalid")
|
|
12082
|
-
};
|
|
12083
|
-
}
|
|
12084
|
-
return {
|
|
12085
|
-
ok: true,
|
|
12086
|
-
value
|
|
12087
|
-
};
|
|
12088
|
-
}
|
|
12089
|
-
//#endregion
|
|
12090
|
-
//#region src/payroll/run-decoding.ts
|
|
12091
|
-
function toPayrollRun(wire) {
|
|
12092
|
-
return {
|
|
12093
|
-
id: toPayrollRunId(wire.id),
|
|
12094
|
-
status: wire.status,
|
|
12095
|
-
periodStart: wire.periodStart,
|
|
12096
|
-
periodEnd: wire.periodEnd,
|
|
12097
|
-
total: wire.total,
|
|
12098
|
-
recipientCount: wire.recipientCount
|
|
12099
|
-
};
|
|
12100
|
-
}
|
|
12101
|
-
function toPayrollRuns(wire) {
|
|
12102
|
-
return {
|
|
12103
|
-
...mapPage(wire, toPayrollRun),
|
|
12104
|
-
settledThisMonth: wire.settledThisMonth
|
|
12105
|
-
};
|
|
12106
|
-
}
|
|
12107
|
-
function toPayrollGroup(wire) {
|
|
12108
|
-
return {
|
|
12109
|
-
id: toPayrollGroupId(wire.id),
|
|
12110
|
-
name: wire.name,
|
|
12111
|
-
tone: wire.tone,
|
|
12112
|
-
members: wire.members.map((member) => ({
|
|
12113
|
-
partyId: member.partyId,
|
|
12114
|
-
amount: member.amount,
|
|
12115
|
-
currency: member.currency
|
|
12116
|
-
}))
|
|
12117
|
-
};
|
|
12118
|
-
}
|
|
12119
|
-
function validateRunItems(input) {
|
|
12120
|
-
for (const [index, item] of input.items.entries()) {
|
|
12121
|
-
if (Schema.decodeResult(PayrollRunItemInputSchema, { onExcessProperty: "error" })(item)._tag === "Failure") return Errors.invalidInput(`items.${index}`, "invalid Payroll item");
|
|
12122
|
-
try {
|
|
12123
|
-
const exact = toExactAssetAmount(item.amount, CONFIGURED_MONEY_ASSETS);
|
|
12124
|
-
const net = BigInt(item.net);
|
|
12125
|
-
const gross = BigInt(item.gross);
|
|
12126
|
-
const adjustments = item.adjustments.reduce((sum, row) => sum + BigInt(row.amount), 0n);
|
|
12127
|
-
if (item.amount.asset !== input.asset || item.net !== exact.minorUnits || gross + adjustments !== net) return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
|
|
12128
|
-
} catch {
|
|
12129
|
-
return Errors.invalidInput(`items.${index}`, "invalid Payroll units");
|
|
12130
|
-
}
|
|
12131
|
-
}
|
|
12132
|
-
return null;
|
|
12133
|
-
}
|
|
12134
|
-
const RunInputSchema = Schema.Struct({
|
|
12135
|
-
permissionId: AuthorizePayrollRunExecutionInputSchema.fields.permissionId,
|
|
12136
|
-
asset: AuthorizePayrollRunExecutionInputSchema.fields.asset,
|
|
12137
|
-
period: AuthorizePayrollRunExecutionInputSchema.fields.period,
|
|
12138
|
-
items: AuthorizePayrollRunExecutionInputSchema.fields.items,
|
|
12139
|
-
requestKey: Schema.optional(AuthorizePayrollRunExecutionInputSchema.fields.requestKey)
|
|
12140
|
-
});
|
|
12141
|
-
/** Decode caller input before session or signer work. Keep routing outside the input. */
|
|
12142
|
-
function decodePayrollRunInput(value) {
|
|
12143
|
-
try {
|
|
12144
|
-
const decoded = Schema.decodeUnknownSync(RunInputSchema, { onExcessProperty: "error" })(value);
|
|
12145
|
-
const input = {
|
|
12146
|
-
permissionId: decoded.permissionId,
|
|
12147
|
-
asset: decoded.asset,
|
|
12148
|
-
period: decoded.period,
|
|
12149
|
-
...decoded.requestKey === void 0 ? {} : { requestKey: decoded.requestKey },
|
|
12150
|
-
items: decoded.items.map((item) => ({
|
|
12151
|
-
amount: item.amount,
|
|
12152
|
-
gross: item.gross,
|
|
12153
|
-
net: item.net,
|
|
12154
|
-
adjustments: item.adjustments,
|
|
12155
|
-
partyId: toPartyId(item.partyId),
|
|
12156
|
-
to: item.to.kind === "party" ? {
|
|
12157
|
-
kind: "party",
|
|
12158
|
-
partyId: toPartyId(item.to.partyId)
|
|
12159
|
-
} : item.to.kind === "address" ? {
|
|
12160
|
-
kind: "address",
|
|
12161
|
-
address: toAddress(item.to.address)
|
|
12162
|
-
} : item.to
|
|
12163
|
-
}))
|
|
12164
|
-
};
|
|
12165
|
-
if (input.items.length === 0 || input.period.start > input.period.end) throw Errors.invalidInput("input", "Payroll requires items and an ordered period");
|
|
12166
|
-
const error = validateRunItems(input);
|
|
12167
|
-
if (error !== null) throw error;
|
|
12168
|
-
return input;
|
|
12169
|
-
} catch (cause) {
|
|
12170
|
-
throw cause instanceof CapxulError ? cause : Errors.invalidInput("input", "Invalid Payroll input");
|
|
12171
|
-
}
|
|
12172
|
-
}
|
|
12173
|
-
/** Read only the exact backend prepare receipt, never an arbitrary signer failure. */
|
|
12174
|
-
function payrollResolution(error, requestKey) {
|
|
12175
|
-
const details = error.details;
|
|
12176
|
-
if (error.code !== "WRONG_STATE" || details?.method !== runs.authorize.name || details.requestKey !== requestKey || typeof details.commandId !== "string" || typeof details.runId !== "string") return void 0;
|
|
12177
|
-
return details.payrollResolution === "failed" || details.payrollResolution === "settled" ? details.payrollResolution : void 0;
|
|
12178
|
-
}
|
|
12179
|
-
function validatePayrollRunLookup(orgId, runId) {
|
|
12180
|
-
return !Schema.is(PayrollRunIdSchema)(runId) || orgId.length === 0 ? {
|
|
12181
|
-
ok: false,
|
|
12182
|
-
error: Errors.invalidInput("runId", "Organization and run ID must be valid")
|
|
12183
|
-
} : {
|
|
12184
|
-
ok: true,
|
|
12185
|
-
value: void 0
|
|
12186
|
-
};
|
|
12187
|
-
}
|
|
12188
|
-
/** Strict saved detail decoder shared by the read and fresh stream. */
|
|
12189
|
-
function decodePayrollRunDetail(raw, orgId, runId) {
|
|
12190
|
-
const decoded = Schema.decodeUnknownResult(Schema.NullOr(PayrollRunDetailSchema), { onExcessProperty: "error" })(raw);
|
|
12191
|
-
if (decoded._tag === "Failure") return {
|
|
12192
|
-
ok: false,
|
|
12193
|
-
error: Errors.invalidInput("run", "Invalid Payroll run detail")
|
|
12194
|
-
};
|
|
12195
|
-
return checkPayrollRunDetail(decoded.success, orgId, runId);
|
|
12196
|
-
}
|
|
12197
|
-
//#endregion
|
|
12198
|
-
//#region src/payroll/run-subscription.ts
|
|
12199
|
-
function detailValue(result) {
|
|
12200
|
-
if (!result.ok) throw result.error;
|
|
12201
|
-
return result.value;
|
|
12202
|
-
}
|
|
12203
|
-
function subscribePayrollRun(deps, orgId, runId, listener, options) {
|
|
12204
|
-
const operation = CAPXUL_OPERATIONS.org.payroll.subscribeRun;
|
|
12205
|
-
const valid = deps.validate(orgId, runId);
|
|
12206
|
-
if (!valid.ok) return runCapxulEffect(Effect.fail(valid.error), observationOnlyControls(deps.invocationControls?.(options) ?? options), operation, deps.runPromise);
|
|
12207
|
-
const decode = (raw) => detailValue(deps.decode(raw, orgId, runId));
|
|
12208
|
-
return subscribeFreshQuery(deps, {
|
|
12209
|
-
orgId,
|
|
12210
|
-
runId
|
|
12211
|
-
}, listener, operation, {
|
|
12212
|
-
decode,
|
|
12213
|
-
validateCached: (raw) => {
|
|
12214
|
-
decode(raw);
|
|
12215
|
-
},
|
|
12216
|
-
invalidValue: (cause) => isCapxulError(cause) ? cause : Errors.invalidInput("run", "Invalid Payroll run detail"),
|
|
12217
|
-
invalidScope: () => Errors.invalidInput("run", "Payroll run subscription scope does not match"),
|
|
12218
|
-
lostValue: () => Errors.invalidInput("run", "Payroll run is no longer readable"),
|
|
12219
|
-
unavailable: () => Errors.notImplemented("payroll", "subscribeRun")
|
|
12220
|
-
}, options);
|
|
12221
|
-
}
|
|
12222
|
-
//#endregion
|
|
12223
|
-
//#region src/payroll/run-authorization.ts
|
|
12224
|
-
/**
|
|
12225
|
-
* Authorize one payroll run: a stable request key, the prepared batch checked
|
|
12226
|
-
* against the run the caller asked for, the signature, and the submission.
|
|
12227
|
-
*/
|
|
12228
|
-
async function authorizePayrollRun(scope, input, controls, onRequestKey, onResolved) {
|
|
12229
|
-
const { deps, orgId, requestKeyScope } = scope;
|
|
12230
|
-
const signal = controls?.signal;
|
|
12231
|
-
const execution = executionDependencies(deps);
|
|
12232
|
-
if (execution === null) return {
|
|
12233
|
-
ok: false,
|
|
12234
|
-
error: Errors.notImplemented("payroll", "executionComposition")
|
|
12235
|
-
};
|
|
12236
|
-
try {
|
|
12237
|
-
input = decodePayrollRunInput(input);
|
|
12238
|
-
} catch (cause) {
|
|
12239
|
-
return {
|
|
12240
|
-
ok: false,
|
|
12241
|
-
error: asCapxulError(cause)
|
|
12242
|
-
};
|
|
12243
|
-
}
|
|
12244
|
-
const { requestKey: provided, ...canonicalInput } = input;
|
|
12245
|
-
const actorId = onRequestKey === void 0 ? void 0 : execution.actor.authSession()?.authUserId;
|
|
12246
|
-
if (onRequestKey !== void 0 && actorId === void 0) return {
|
|
12247
|
-
ok: false,
|
|
12248
|
-
error: Errors.notAuthenticated()
|
|
12249
|
-
};
|
|
12250
|
-
const requestKey = await paymentRequestKeyLifecycle(`${requestKeyScope}:${AUTHORIZE_RUN}`, onRequestKey === void 0 ? {
|
|
12251
|
-
orgId,
|
|
12252
|
-
input
|
|
12253
|
-
} : {
|
|
12254
|
-
actorId,
|
|
12255
|
-
orgId,
|
|
12256
|
-
input: canonicalInput
|
|
12257
|
-
}, provided, onRequestKey === void 0 ? void 0 : { manageProvidedKey: true });
|
|
12258
|
-
try {
|
|
12259
|
-
await onRequestKey?.(requestKey.key);
|
|
12260
|
-
if (onRequestKey !== void 0 && execution.actor.authSession()?.authUserId !== actorId) {
|
|
12261
|
-
await requestKey.finish(false).catch(() => void 0);
|
|
12262
|
-
return {
|
|
12263
|
-
ok: false,
|
|
12264
|
-
error: Errors.notAuthenticated()
|
|
12265
|
-
};
|
|
12266
|
-
}
|
|
12267
|
-
} catch (cause) {
|
|
12268
|
-
await requestKey.finish(false).catch(() => void 0);
|
|
12269
|
-
return {
|
|
12270
|
-
ok: false,
|
|
12271
|
-
error: asCapxulError(cause)
|
|
12272
|
-
};
|
|
12273
|
-
}
|
|
12274
|
-
let authorized;
|
|
12275
|
-
let acceptedReplay;
|
|
12276
|
-
let preparedClosed = false;
|
|
12277
|
-
let terminalReplay;
|
|
12278
|
-
const submitted = await executePrepared(execution, async (signerAddress) => {
|
|
12279
|
-
const prepared = await runIfActive(signal, AUTHORIZE_RUN, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.authorize, copyInvocationObservation(controls, {
|
|
12280
|
-
orgId,
|
|
12281
|
-
permissionId: input.permissionId,
|
|
12282
|
-
asset: input.asset,
|
|
12283
|
-
requestKey: requestKey.key,
|
|
12284
|
-
signerAddress,
|
|
12285
|
-
period: input.period,
|
|
12286
|
-
items: input.items
|
|
12287
|
-
}))).pipe(Effect.mapError((publicError) => ({ publicError }))), controls), deps.runPromise);
|
|
12288
|
-
if (!prepared.ok) {
|
|
12289
|
-
terminalReplay = payrollResolution(prepared.error, requestKey.key);
|
|
12290
|
-
return prepared;
|
|
12291
|
-
}
|
|
12292
|
-
authorized = prepared.value.run;
|
|
12293
|
-
acceptedReplay = prepared.value.replay.kind === "accepted" ? prepared.value.replay.submission : void 0;
|
|
12294
|
-
return {
|
|
12295
|
-
ok: true,
|
|
12296
|
-
value: prepared.value.execution
|
|
12297
|
-
};
|
|
12298
|
-
}, (request, evidence) => authorized === void 0 || authorized.periodStart !== input.period.start || authorized.periodEnd !== input.period.end ? Promise.resolve(false) : isRunBatch({
|
|
12299
|
-
orgId,
|
|
12300
|
-
permissionId: input.permissionId,
|
|
12301
|
-
requestKey: requestKey.key,
|
|
12302
|
-
runId: authorized.id,
|
|
12303
|
-
asset: input.asset,
|
|
12304
|
-
period: input.period,
|
|
12305
|
-
items: input.items
|
|
12306
|
-
})(request, evidence), true, () => preparedClosed = true, signal, controls, void 0, () => acceptedReplay);
|
|
12307
|
-
const resolution = preparedClosed ? "failed" : terminalReplay;
|
|
12308
|
-
if (resolution !== void 0) try {
|
|
12309
|
-
await onResolved?.(resolution);
|
|
12310
|
-
} catch {}
|
|
12311
|
-
try {
|
|
12312
|
-
await requestKey.finish(submitted.ok || resolution !== void 0);
|
|
12313
|
-
} catch {}
|
|
12314
|
-
if (!submitted.ok) return submitted;
|
|
12315
|
-
return authorized === void 0 ? {
|
|
12316
|
-
ok: false,
|
|
12317
|
-
error: Errors.unknown()
|
|
12318
|
-
} : {
|
|
12319
|
-
ok: true,
|
|
12320
|
-
value: toPayrollRun(authorized)
|
|
12321
|
-
};
|
|
12322
|
-
}
|
|
12323
|
-
const AUTHORIZE_RUN = runs.authorize.name;
|
|
12324
|
-
function executionDependencies(deps) {
|
|
12325
|
-
return deps.actor === void 0 || deps.chainId === void 0 || deps.signer === void 0 ? null : {
|
|
12326
|
-
actor: deps.actor,
|
|
12327
|
-
chainId: deps.chainId,
|
|
12328
|
-
convexCall: deps.convexCall,
|
|
12329
|
-
runPromise: deps.runPromise,
|
|
12330
|
-
signer: deps.signer,
|
|
12331
|
-
...deps.telemetry === void 0 ? {} : { telemetry: deps.telemetry }
|
|
12332
|
-
};
|
|
12333
|
-
}
|
|
12334
|
-
//#endregion
|
|
12335
|
-
//#region src/payroll/runs.ts
|
|
12336
|
-
function makePayrollMethods(deps, orgId) {
|
|
12337
|
-
const requestKeyScope = deps.requestKeyScope ?? `client-${crypto.randomUUID()}`;
|
|
12338
|
-
/** One observed call of one payroll row, its reply projected inside the call, under the row's name. */
|
|
12339
|
-
const call = (row, args, options, project) => {
|
|
12340
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
12341
|
-
return runPortEffect(callOperation(deps.convexCall, row, copyInvocationObservation(controls, args)).pipe(Effect.map(project), Effect.mapError((publicError) => ({ publicError }))), controls, row.name, deps.runPromise);
|
|
12342
|
-
};
|
|
12343
|
-
return {
|
|
12344
|
-
subscribeRun: (runId, listener, options) => subscribePayrollRun({
|
|
12345
|
-
actor: deps.actor,
|
|
12346
|
-
convexCall: deps.convexCall,
|
|
12347
|
-
query: reference(runs.get),
|
|
12348
|
-
runPromise: deps.runPromise,
|
|
12349
|
-
invocationControls: deps.invocationControls,
|
|
12350
|
-
decode: decodePayrollRunDetail,
|
|
12351
|
-
validate: validatePayrollRunLookup
|
|
12352
|
-
}, orgId, runId, listener, options),
|
|
12353
|
-
getRun: (runId, options) => {
|
|
12354
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
12355
|
-
const lifetime = deps.actor?.authLifetime();
|
|
12356
|
-
const operation = runs.get.name;
|
|
12357
|
-
return runCapxulEffect(Effect.gen(function* () {
|
|
12358
|
-
const lookup = validatePayrollRunLookup(orgId, runId);
|
|
12359
|
-
if (!lookup.ok) return yield* Effect.fail(lookup.error);
|
|
12360
|
-
if (lifetime === void 0 || lifetime.authUserId === null || deps.actor === void 0) return yield* Effect.fail(Errors.notAuthenticated());
|
|
12361
|
-
const current = () => deps.actor?.matchesAuthLifetime(lifetime, lifetime.authUserId) === true;
|
|
12362
|
-
if (!current()) return yield* Effect.fail(Errors.notAuthenticated());
|
|
12363
|
-
const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
|
|
12364
|
-
const captured = carryInvocationParentSpan(controls, parent);
|
|
12365
|
-
const run = yield* awaitCapxulResult(() => runIfActive(controls?.signal, operation, () => withInvocationParentSpan(Effect.suspend(() => callOperation(deps.convexCall, runs.get, copyInvocationObservation(captured, {
|
|
12366
|
-
orgId,
|
|
12367
|
-
runId
|
|
12368
|
-
}))).pipe(Effect.mapError((publicError) => ({ publicError }))), captured), deps.runPromise));
|
|
12369
|
-
if (!current()) return yield* Effect.fail(Errors.cancelled({ operation }));
|
|
12370
|
-
const checked = checkPayrollRunDetail(run, orgId, runId);
|
|
12371
|
-
return yield* checked.ok ? Effect.succeed(checked.value) : Effect.fail(checked.error);
|
|
12372
|
-
}), observationOnlyControls(controls), operation, deps.runPromise);
|
|
12373
|
-
},
|
|
12374
|
-
runs: (options) => call(runs.list, { orgId }, options, toPayrollRuns),
|
|
12375
|
-
authorizeRun: (input, options) => {
|
|
12376
|
-
const controls = deps.invocationControls?.(options) ?? options;
|
|
12377
|
-
return runCapxulEffect(Effect.gen(function* () {
|
|
12378
|
-
if (controls?.signal?.aborted === true) return yield* Effect.fail(Errors.cancelled({ operation: AUTHORIZE_RUN }));
|
|
12379
|
-
const parent = yield* Effect.currentSpan.pipe(Effect.orDie);
|
|
12380
|
-
return yield* awaitCapxulResult(() => authorizePayrollRun({
|
|
12381
|
-
deps,
|
|
12382
|
-
orgId,
|
|
12383
|
-
requestKeyScope
|
|
12384
|
-
}, input, carryInvocationParentSpan(controls, parent), options?.onRequestKey, options?.onResolved));
|
|
12385
|
-
}), observationOnlyControls(controls), AUTHORIZE_RUN, deps.runPromise);
|
|
12386
|
-
},
|
|
12387
|
-
terms: (options) => call(terms.list, { orgId }, options, (rates) => rates),
|
|
12388
|
-
groups: {
|
|
12389
|
-
list: (options) => call(groups.list, { orgId }, options, (saved) => mapPage(saved, toPayrollGroup)),
|
|
12390
|
-
save: (input, options) => call(groups.save, {
|
|
12391
|
-
orgId,
|
|
12392
|
-
...input.id === void 0 ? {} : { id: String(input.id) },
|
|
12393
|
-
name: input.name,
|
|
12394
|
-
tone: input.tone,
|
|
12395
|
-
members: input.members
|
|
12396
|
-
}, options, toPayrollGroup),
|
|
12397
|
-
remove: (id, options) => call(groups.remove, {
|
|
12398
|
-
orgId,
|
|
12399
|
-
id
|
|
12400
|
-
}, options, () => void 0)
|
|
12401
|
-
}
|
|
12402
|
-
};
|
|
12403
|
-
}
|
|
12404
|
-
const payrollUnavailable = () => Promise.resolve({
|
|
12405
|
-
ok: false,
|
|
12406
|
-
error: Errors.notImplemented("payroll", "executionComposition")
|
|
12407
|
-
});
|
|
12408
|
-
/** The bundle an unconfigured client exposes — every verb refuses, none throws. */
|
|
12409
|
-
function makeUnavailablePayrollMethods() {
|
|
12410
|
-
return {
|
|
12411
|
-
getRun: payrollUnavailable,
|
|
12412
|
-
subscribeRun: payrollUnavailable,
|
|
12413
|
-
runs: payrollUnavailable,
|
|
12414
|
-
authorizeRun: payrollUnavailable,
|
|
12415
|
-
terms: payrollUnavailable,
|
|
12416
|
-
groups: {
|
|
12417
|
-
list: payrollUnavailable,
|
|
12418
|
-
save: payrollUnavailable,
|
|
12419
|
-
remove: payrollUnavailable
|
|
12420
|
-
}
|
|
12421
|
-
};
|
|
12422
|
-
}
|
|
12423
|
-
//#endregion
|
|
12424
11227
|
//#region src/signing/embedded-wallet.ts
|
|
12425
11228
|
function openfortEmbeddedWalletPort(input) {
|
|
12426
11229
|
return {
|
|
@@ -15789,6 +14592,11 @@ const OBSERVED_CONVEX_QUERIES = new Set(OPERATIONS.filter((row) => row.kind ===
|
|
|
15789
14592
|
const OBSERVED_CONVEX_MUTATIONS = new Set(OPERATIONS.filter((row) => row.kind === "mutation").map((row) => row.path));
|
|
15790
14593
|
//#endregion
|
|
15791
14594
|
//#region src/platform/transport/convex-errors.ts
|
|
14595
|
+
/**
|
|
14596
|
+
* All a redacting backend says about an error it raised itself: its request
|
|
14597
|
+
* id. The WebSocket client wraps it as `[CONVEX Q(path)] … Called by client`.
|
|
14598
|
+
*/
|
|
14599
|
+
const REDACTED_ERROR = /^(?:\[CONVEX [QMA?]\([^)]*\)\] )?\[Request ID: ([^\]\s]+)\] Server Error(?:\s+Called by client)?$/u;
|
|
15792
14600
|
function mapToCapxulError(operation, err, transport) {
|
|
15793
14601
|
const decoded = decodeConvexError(err);
|
|
15794
14602
|
if (decoded !== null) return decoded;
|
|
@@ -15813,9 +14621,26 @@ function mapToCapxulError(operation, err, transport) {
|
|
|
15813
14621
|
* anything else (an uncaught "Server Error") is a backend defect. None is a
|
|
15814
14622
|
* provider outage. The Convex request id rides in details, so the backend's
|
|
15815
14623
|
* log line can be found.
|
|
14624
|
+
*
|
|
14625
|
+
* A deployed backend redacts these errors (`REDACT_LOGS_TO_CLIENT`, #2304):
|
|
14626
|
+
* the message is only the request id, and no structured cause exists, so the
|
|
14627
|
+
* message shape is the only discriminator. Neither case can be told apart, and
|
|
14628
|
+
* the reply says where to look instead. The text matches stay for a backend
|
|
14629
|
+
* that does not redact (development, local).
|
|
15816
14630
|
*/
|
|
15817
14631
|
function backendFailure(operation, err) {
|
|
15818
14632
|
const text = err instanceof Error ? err.message : String(err);
|
|
14633
|
+
const redacted = REDACTED_ERROR.exec(text.trim())?.[1];
|
|
14634
|
+
if (redacted !== void 0) return new CapxulError("UNKNOWN", `The backend refused ${operation} and gave no detail. Find Convex request ${redacted} in the backend logs. If the backend can be newer than this client, update the CLI or SDK.`, {
|
|
14635
|
+
cause: err,
|
|
14636
|
+
layer: "convex",
|
|
14637
|
+
details: {
|
|
14638
|
+
operation,
|
|
14639
|
+
provider: "convex",
|
|
14640
|
+
requestId: redacted,
|
|
14641
|
+
reason: "backend-error-redacted"
|
|
14642
|
+
}
|
|
14643
|
+
});
|
|
15819
14644
|
const requestId = /\[Request ID: ([^\]\s]+)\]/u.exec(text)?.[1];
|
|
15820
14645
|
const where = {
|
|
15821
14646
|
operation,
|
|
@@ -18461,4 +17286,4 @@ async function createCapxulClient(input) {
|
|
|
18461
17286
|
return createCapxulClientWithSignerControls(input, {});
|
|
18462
17287
|
}
|
|
18463
17288
|
//#endregion
|
|
18464
|
-
export {
|
|
17289
|
+
export { cloneTelemetryEvent as a, identityErrorFromCapxul as c, devPrivateKeySigner as d, createOpenfortBrowserSigner as f, decodePermissionIntent as g, openfortEmbeddedWalletPort as h, postHogObservability as i, bootstrapErrorFromCapxul as l, openfortEmbeddedSignerFromWallet as m, createCapxulClientWithSignerControls as n, cloneTelemetryProps as o, embeddedSigner as p, assembleCapxulClient as r, IdentityPortTag as s, createCapxulClient as t, deriveDevPrivateKey as u };
|