@capxul/cli 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/main.mjs CHANGED
@@ -11120,6 +11120,9 @@ const moneyExecutionContract = {
11120
11120
  //#region ../../packages/sdk/src/ports/indexer-read.ts
11121
11121
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
11122
11122
  const INDEXER_MAX_LIMIT = 1e3;
11123
+ function validTime(value) {
11124
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
11125
+ }
11123
11126
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
11124
11127
  /**
11125
11128
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -11127,12 +11130,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
11127
11130
  * against the hermetic adapter cannot pass on input production rejects. Throws
11128
11131
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
11129
11132
  */
11133
+ function assertEventLookup(input, id) {
11134
+ 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");
11135
+ }
11130
11136
  function assertIndexerPage(input) {
11131
11137
  const safe = String(input.safe).toLowerCase();
11132
11138
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
11133
11139
  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}`);
11134
11140
  const offset = input.offset ?? 0;
11135
11141
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
11142
+ const timeline = input.timeline;
11143
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
11144
+ 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");
11145
+ if (timeline !== void 0) {
11146
+ 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");
11147
+ }
11136
11148
  return safe;
11137
11149
  }
11138
11150
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -11162,20 +11174,6 @@ function directionFor(safe, transfer) {
11162
11174
  if (from === safe && to === safe) return "self";
11163
11175
  return to === safe ? "in" : "out";
11164
11176
  }
11165
- /**
11166
- * The Transfer a protocol Payment moved is the same money as its `payment`
11167
- * row. One actor sees one item for it, so the Transfer is claimed and does not
11168
- * become a second row. Another transfer in the same transaction that this
11169
- * payment did not move stays unclaimed and keeps its own row.
11170
- */
11171
- function claimedTransferIds(transfers, payments) {
11172
- const claimed = /* @__PURE__ */ new Set();
11173
- for (const payment of payments) {
11174
- 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());
11175
- if (match !== void 0) claimed.add(match.id);
11176
- }
11177
- return claimed;
11178
- }
11179
11177
  function withAnnotation(item, annotation) {
11180
11178
  return annotation?.counterpartyLabel === void 0 ? item : {
11181
11179
  ...item,
@@ -11186,10 +11184,14 @@ function withAnnotation(item, annotation) {
11186
11184
  * An indexed settlement confirms the intent. A missing row can be outside
11187
11185
  * this page or still backfilling; only the correction owner can prove rollback.
11188
11186
  */
11189
- function confirmPayment(intent, chain) {
11190
- if (chain !== void 0) return {
11187
+ function confirmPayment(intent, chain, expected) {
11188
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
11191
11189
  ...intent,
11192
11190
  status: "settled",
11191
+ ...intent.execution === void 0 ? {} : { execution: {
11192
+ status: "settled",
11193
+ retryRequired: false
11194
+ } },
11193
11195
  receipt: {
11194
11196
  chainId: chain.chainId,
11195
11197
  transactionHash: chain.txHash
@@ -11198,16 +11200,22 @@ function confirmPayment(intent, chain) {
11198
11200
  };
11199
11201
  return intent;
11200
11202
  }
11203
+ function matchesPaymentFact(chain, expected) {
11204
+ 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;
11205
+ }
11201
11206
  /**
11202
11207
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
11203
11208
  * denominate its asset. The indexer serves only configured assets, so a row
11204
11209
  * the registry does not know means the SDK and that deployment disagree; it is
11205
11210
  * dropped rather than shown with a guessed currency.
11206
11211
  */
11212
+ function isActivityVisible(item) {
11213
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
11214
+ }
11207
11215
  function transferItem(safe, transfer, annotation) {
11208
11216
  const asset = configuredMoneyAssetById(transfer.assetId);
11209
11217
  if (asset === null) return null;
11210
- const direction = directionFor(safe, transfer);
11218
+ const direction = directionFor(safe.toLowerCase(), transfer);
11211
11219
  const counterparty = direction === "in" ? transfer.from : transfer.to;
11212
11220
  const at = blockMillis(transfer);
11213
11221
  return withAnnotation({
@@ -11229,47 +11237,6 @@ function transferItem(safe, transfer, annotation) {
11229
11237
  updatedAt: at
11230
11238
  }, annotation);
11231
11239
  }
11232
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
11233
- function compareItems(left, right) {
11234
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
11235
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
11236
- if (left.id === right.id) return 0;
11237
- return left.id < right.id ? 1 : -1;
11238
- }
11239
- function stitchActivity(input) {
11240
- const safe = input.safe.toLowerCase();
11241
- const chainBySettlement = /* @__PURE__ */ new Map();
11242
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
11243
- const chainByPaymentId = /* @__PURE__ */ new Map();
11244
- for (const link of input.links) {
11245
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
11246
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
11247
- }
11248
- const annotations = /* @__PURE__ */ new Map();
11249
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
11250
- const items = [];
11251
- const claiming = [];
11252
- for (const intent of input.intents) {
11253
- if (intent.kind !== "payment") continue;
11254
- const chain = chainByPaymentId.get(intent.id);
11255
- if (chain !== void 0) claiming.push(chain);
11256
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
11257
- }
11258
- const claimed = claimedTransferIds(input.transfers, claiming);
11259
- for (const transfer of input.transfers) {
11260
- if (claimed.has(transfer.id)) continue;
11261
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
11262
- if (item !== null) items.push(item);
11263
- }
11264
- return items.sort(compareItems);
11265
- }
11266
- /** The reference ids one stitched page needs its Convex overlay for. */
11267
- function overlayReferences(items) {
11268
- return items.map((item) => ({
11269
- kind: item.kind,
11270
- id: item.id
11271
- }));
11272
- }
11273
11240
  /**
11274
11241
  * Money in, money out and the pending share, one bucket per currency and
11275
11242
  * precision. Two assets that peg to the same currency at different precision
@@ -11288,7 +11255,7 @@ function summarizeItems(items) {
11288
11255
  };
11289
11256
  const raw = rawUnits(item.amount.value, item.amount.decimals);
11290
11257
  if (item.kind === "payment" && item.status !== "settled") {
11291
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
11258
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
11292
11259
  } else if (item.direction === "in") bucket.in += raw;
11293
11260
  else if (item.direction === "out") bucket.out += raw;
11294
11261
  buckets.set(key, bucket);
@@ -11311,179 +11278,412 @@ function rawUnits(value, decimals) {
11311
11278
  }
11312
11279
  //#endregion
11313
11280
  //#region ../../packages/sdk/src/surface/activity-read.ts
11314
- /** How many Convex intent rows one page reads for its labels and pending rows. */
11315
- const INTENT_WINDOW = 100;
11316
- /** movement/annotations:overlay bounds each identifier array to 200. */
11281
+ const BATCH = 100;
11317
11282
  const MAX_OVERLAY_IDS = 200;
11318
- /** The window a summary aggregates over. The indexer's own page bound. */
11319
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
11320
- function parseCursor(value) {
11321
- if (value === void 0) return 0;
11322
- try {
11323
- const parsed = JSON.parse(value);
11324
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
11325
- } catch {
11326
- return 0;
11327
- }
11283
+ function actorKey(actor) {
11284
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
11285
+ }
11286
+ function scopeKey(scope) {
11287
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
11288
+ }
11289
+ function selectionKey(params) {
11290
+ const filter = params?.filter;
11291
+ return JSON.stringify([
11292
+ params?.range?.from ?? null,
11293
+ params?.range?.to ?? null,
11294
+ filter?.kind ?? null,
11295
+ filter?.direction ?? null,
11296
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
11297
+ ]);
11298
+ }
11299
+ function validString(value, max) {
11300
+ return typeof value === "string" && value.length <= max;
11301
+ }
11302
+ function validPosition(position) {
11303
+ 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);
11304
+ }
11305
+ function parseCursor(value, scope, selection) {
11306
+ if (value === void 0) return null;
11307
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
11308
+ const cursor = JSON.parse(value);
11309
+ 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");
11310
+ return cursor;
11328
11311
  }
11329
- function inRange(item, range) {
11330
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
11331
- return range?.to === void 0 || item.createdAt <= range.to;
11312
+ function requireParams(params) {
11313
+ const limit = params?.limit ?? 25;
11314
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
11315
+ 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");
11316
+ const f = params?.filter;
11317
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
11318
+ "in",
11319
+ "out",
11320
+ "self"
11321
+ ].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");
11332
11322
  }
11333
- /** The caller's filter applied to the merged page. The join owns the order. */
11334
11323
  function matchesFilter(item, filter) {
11335
- if (filter === void 0) return true;
11336
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
11337
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
11338
- if (filter.status === void 0) return true;
11339
- return item.kind === "payment" && filter.status.includes(item.status);
11324
+ 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));
11340
11325
  }
11341
11326
  function overlay(deps, actor, controls, input) {
11342
11327
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
11343
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
11328
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
11344
11329
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
11345
11330
  ...actor === void 0 ? {} : { actor },
11346
11331
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
11347
11332
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
11348
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
11349
- ...pages[0],
11350
- links: pages.flatMap((page) => page.links),
11351
- annotations: pages.flatMap((page) => page.annotations)
11352
- })));
11333
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
11334
+ const first = pages[0];
11335
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
11336
+ return Effect.succeed({
11337
+ ...first,
11338
+ links: pages.flatMap((page) => page.links),
11339
+ annotations: pages.flatMap((page) => page.annotations),
11340
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
11341
+ });
11342
+ }));
11353
11343
  }
11354
11344
  function convexPage(deps, actor, controls, input) {
11355
11345
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
11356
- ...actor === void 0 ? {} : { actor },
11357
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
11358
- ...input.limit === void 0 ? {} : { limit: input.limit },
11359
- ...input.range === void 0 ? {} : { range: input.range }
11346
+ ...input,
11347
+ actor
11360
11348
  } }));
11361
11349
  }
11362
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
11363
- function chainRows(deps, safe, limit) {
11364
- const page = {
11365
- safe,
11366
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
11367
- };
11368
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
11369
- transfers,
11370
- payments
11371
- })), Effect.catch(() => Effect.succeed(null)));
11350
+ function chainRead(deps, table, input) {
11351
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
11352
+ if (error.publicError.details?.reason === "scope-refused" || ![
11353
+ "NETWORK_ERROR",
11354
+ "RATE_LIMITED",
11355
+ "PROVIDER_ERROR",
11356
+ "CAPABILITY_UNAVAILABLE",
11357
+ "TIMEOUT"
11358
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
11359
+ return Effect.succeed(null);
11360
+ }));
11372
11361
  }
11373
- /** The stitched, filtered rows of one window, newest first. */
11374
- function stitchedWindow(deps, actor, controls, window, params) {
11362
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
11375
11363
  return Effect.gen(function* () {
11376
- const scope = yield* overlay(deps, actor, controls, {});
11377
- const convex = yield* convexPage(deps, actor, controls, {
11378
- limit: INTENT_WINDOW,
11379
- ...params.range === void 0 ? {} : { range: params.range }
11364
+ const page = yield* convexPage(deps, scope.actor, controls, {
11365
+ limit: params?.limit ?? 25,
11366
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
11367
+ range: {
11368
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
11369
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
11370
+ },
11371
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
11380
11372
  });
11381
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
11382
- if (rows === null || scope.safe === null) {
11383
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
11384
- read_source: "convex",
11385
- read_warning: "indexer-unavailable"
11386
- });
11387
- return {
11388
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
11389
- stitched: false,
11390
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
11391
- };
11373
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
11374
+ kind: item.kind,
11375
+ id: item.id
11376
+ })) });
11377
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
11378
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
11379
+ yield* Effect.annotateCurrentSpan({
11380
+ read_source: "convex",
11381
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
11382
+ });
11383
+ return {
11384
+ ...page,
11385
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
11386
+ checkpoint: state.checkpoint,
11387
+ cursor: page.cursor === null ? null : JSON.stringify({
11388
+ ...state,
11389
+ source: "fallback",
11390
+ paymentCursor: page.cursor,
11391
+ degraded
11392
+ }),
11393
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
11394
+ };
11395
+ });
11396
+ }
11397
+ function boundedRange(range, checkpoint) {
11398
+ return {
11399
+ ...range?.from === void 0 ? {} : { from: range.from },
11400
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
11401
+ };
11402
+ }
11403
+ function unavailablePage(deps, scope, controls, params, state, first) {
11404
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
11405
+ return Effect.annotateCurrentSpan({
11406
+ read_source: "indexer",
11407
+ read_warning: "indexer-unavailable"
11408
+ }).pipe(Effect.as({
11409
+ items: [],
11410
+ cursor: null,
11411
+ checkpoint: state.checkpoint,
11412
+ observedAt: Date.now(),
11413
+ readWarning: "indexer-unavailable"
11414
+ }));
11415
+ }
11416
+ function readTransfers(deps, safe, range, state, wanted, first) {
11417
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
11418
+ const to = Math.floor(range.to / 1e3);
11419
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
11420
+ return chainRead(deps, "transfer", {
11421
+ safe: toAddress(safe),
11422
+ limit: wanted ? BATCH : 1,
11423
+ timeline: {
11424
+ from,
11425
+ to,
11426
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
11392
11427
  }
11393
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
11394
- const decoration = yield* overlay(deps, actor, controls, {
11395
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
11428
+ });
11429
+ }
11430
+ function readPayments(deps, scope, controls, range, state, wanted) {
11431
+ const done = state.paymentDone || !wanted;
11432
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
11433
+ items: [],
11434
+ pending: state.paymentPending,
11435
+ cursor: state.paymentCursor,
11436
+ done
11437
+ });
11438
+ return convexPage(deps, scope.actor, controls, {
11439
+ limit: BATCH,
11440
+ range,
11441
+ filter: { kind: "payment" },
11442
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
11443
+ }).pipe(Effect.map((page) => ({
11444
+ items: page.items,
11445
+ pending: page.items.map((item) => item.id),
11446
+ cursor: page.cursor,
11447
+ done: page.cursor === null
11448
+ })));
11449
+ }
11450
+ function readPaymentFacts(deps, safe, settlements) {
11451
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
11452
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
11453
+ safe: toAddress(safe),
11454
+ limit: 50,
11455
+ settlements: settlements.slice(offset, offset + 50)
11456
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
11457
+ }
11458
+ function decoratePage(deps, scope, controls, transfers, page, range) {
11459
+ return Effect.gen(function* () {
11460
+ const decoration = yield* overlay(deps, scope.actor, controls, {
11461
+ references: [...page.pending.map((id) => ({
11462
+ kind: "payment",
11463
+ id
11464
+ })), ...transfers.map((row) => ({
11396
11465
  kind: "movement",
11397
11466
  id: row.id
11398
11467
  }))],
11399
- settlementIds: rows.payments.map((row) => row.settlementId)
11468
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
11400
11469
  });
11401
- const intents = [...convex.items];
11402
- const missing = new Set(decoration.links.map((link) => link.paymentId));
11403
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
11404
- let cursor = convex.cursor;
11405
- while (missing.size > 0 && cursor !== null) {
11406
- const page = yield* convexPage(deps, actor, controls, {
11407
- limit: INTENT_WINDOW,
11408
- cursor,
11409
- ...params.range === void 0 ? {} : { range: params.range }
11410
- });
11411
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
11412
- cursor = page.cursor;
11470
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
11471
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
11472
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
11473
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
11474
+ if (facts === null) return null;
11475
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
11476
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
11477
+ fact: bySettlement.get(link.settlementId),
11478
+ expected: link.expected
11479
+ }]));
11480
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
11481
+ return {
11482
+ payments: page.pending.flatMap((id) => {
11483
+ const item = byId.get(id);
11484
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
11485
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
11486
+ return isActivityVisible(confirmed) ? [confirmed] : [];
11487
+ }),
11488
+ annotations,
11489
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
11490
+ };
11491
+ });
11492
+ }
11493
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
11494
+ const { payments, annotations, linked } = decoration;
11495
+ let pi = 0;
11496
+ let ti = 0;
11497
+ const items = [];
11498
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
11499
+ let transferBefore = state.transferBefore;
11500
+ while (items.length < limit) {
11501
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
11502
+ const payment = payments[pi];
11503
+ const row = transfers[ti];
11504
+ if (payment === void 0 && row === void 0) break;
11505
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
11506
+ pi += 1;
11507
+ if (matchesFilter(payment, filter)) items.push(payment);
11508
+ } else if (row !== void 0) {
11509
+ ti += 1;
11510
+ transferBefore = {
11511
+ timestamp: Number(row.timestamp),
11512
+ id: row.id
11513
+ };
11514
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
11515
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
11516
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
11413
11517
  }
11414
- const older = intents.slice(convex.items.length);
11415
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
11518
+ }
11519
+ return {
11520
+ items,
11521
+ next: {
11522
+ ...state,
11523
+ paymentCursor: page.cursor,
11524
+ paymentDone: page.done,
11525
+ paymentPending: payments.slice(pi).map((item) => item.id),
11526
+ transferBefore,
11527
+ transferDone: ti === transfers.length && transferEnd
11528
+ }
11529
+ };
11530
+ }
11531
+ function indexedPage(deps, scope, controls, params, state, first) {
11532
+ return Effect.gen(function* () {
11533
+ const range = boundedRange(params?.range, state.checkpoint);
11534
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
11535
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
11536
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
11537
+ const transfers = wantsTransfers ? found : [];
11538
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
11539
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
11540
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
11541
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
11542
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
11543
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
11544
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
11416
11545
  return {
11417
- items: stitchActivity({
11418
- safe: scope.safe,
11419
- transfers: rows.transfers,
11420
- payments: rows.payments,
11421
- intents,
11422
- links: decoration.links,
11423
- annotations: [...decoration.annotations, ...olderDecoration]
11424
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
11425
- stitched: true
11546
+ items,
11547
+ cursor,
11548
+ checkpoint: state.checkpoint,
11549
+ observedAt: Date.now()
11426
11550
  };
11427
11551
  });
11428
11552
  }
11429
11553
  function readActivityPage(deps, actor, controls, params) {
11430
- const limit = params?.limit ?? 25;
11431
- const offset = parseCursor(params?.cursor);
11432
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
11433
- ...params?.range === void 0 ? {} : { range: params.range },
11434
- ...params?.filter === void 0 ? {} : { filter: params.filter }
11435
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
11436
- const observedAt = Date.now();
11437
- if (!stitched) return {
11438
- items: items.slice(0, limit),
11554
+ return Effect.gen(function* () {
11555
+ yield* Effect.try({
11556
+ try: () => requireParams(params),
11557
+ catch: (cause) => ({ publicError: cause })
11558
+ });
11559
+ const scope = yield* overlay(deps, actor, controls, {});
11560
+ const selection = selectionKey(params);
11561
+ const cursor = yield* Effect.try({
11562
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
11563
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
11564
+ });
11565
+ const state = cursor ?? {
11566
+ version: 1,
11567
+ scope: scopeKey(scope),
11568
+ selection,
11569
+ checkpoint: Date.now(),
11570
+ source: "indexed",
11571
+ paymentCursor: null,
11572
+ paymentDone: false,
11573
+ paymentPending: [],
11574
+ transferBefore: null,
11575
+ transferDone: false,
11576
+ degraded: false
11577
+ };
11578
+ const range = boundedRange(params?.range, state.checkpoint);
11579
+ if (range.from !== void 0 && range.from > range.to) return {
11580
+ items: [],
11439
11581
  cursor: null,
11440
- checkpoint: observedAt,
11441
- observedAt,
11442
- ...readWarning === void 0 ? {} : { readWarning }
11582
+ checkpoint: state.checkpoint,
11583
+ observedAt: Date.now()
11443
11584
  };
11585
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
11586
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
11587
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
11588
+ });
11589
+ }
11590
+ function readActivitySummary(deps, actor, controls, params) {
11591
+ return Effect.gen(function* () {
11592
+ const totals = /* @__PURE__ */ new Map();
11593
+ let cursor;
11594
+ let count = 0;
11595
+ let readWarning;
11596
+ do {
11597
+ const page = yield* readActivityPage(deps, actor, controls, {
11598
+ limit: BATCH,
11599
+ ...cursor === void 0 ? {} : { cursor },
11600
+ ...params?.window === void 0 ? {} : { range: params.window }
11601
+ });
11602
+ count += page.items.length;
11603
+ readWarning ??= page.readWarning;
11604
+ for (const total of summarizeItems(page.items)) {
11605
+ const key = `${total.currency}:${total.decimals}`;
11606
+ const old = totals.get(key);
11607
+ totals.set(key, old === void 0 ? total : {
11608
+ ...total,
11609
+ in: String(BigInt(old.in) + BigInt(total.in)),
11610
+ out: String(BigInt(old.out) + BigInt(total.out)),
11611
+ net: String(BigInt(old.net) + BigInt(total.net)),
11612
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
11613
+ });
11614
+ }
11615
+ cursor = page.cursor ?? void 0;
11616
+ } while (cursor !== void 0);
11444
11617
  return {
11445
- items: items.slice(offset, offset + limit),
11446
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
11447
- checkpoint: observedAt,
11448
- observedAt
11618
+ window: {
11619
+ from: params?.window?.from ?? null,
11620
+ to: params?.window?.to ?? null
11621
+ },
11622
+ totals: [...totals.values()],
11623
+ count,
11624
+ observedAt: Date.now(),
11625
+ ...readWarning === void 0 ? {} : { readWarning }
11449
11626
  };
11450
- }));
11627
+ });
11451
11628
  }
11452
- function readActivitySummary(deps, actor, controls, params) {
11453
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
11454
- window: {
11455
- from: params?.window?.from ?? null,
11456
- to: params?.window?.to ?? null
11457
- },
11458
- totals: summarizeItems(items),
11459
- count: items.length,
11460
- observedAt: Date.now(),
11461
- ...readWarning === void 0 ? {} : { readWarning }
11462
- })));
11629
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
11630
+ function readIndexedMovement(deps, actor, controls, eventId) {
11631
+ return Effect.gen(function* () {
11632
+ const scope = yield* overlay(deps, actor, controls, { references: [{
11633
+ kind: "movement",
11634
+ id: eventId
11635
+ }] });
11636
+ if (scope.safe === null) return null;
11637
+ const safe = scope.safe.toLowerCase();
11638
+ const row = (yield* deps.indexer.read("transfer", {
11639
+ safe: toAddress(safe),
11640
+ limit: 1,
11641
+ eventId
11642
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
11643
+ if (row === void 0) return null;
11644
+ const blockNumber = Number(row.blockNumber);
11645
+ 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") });
11646
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
11647
+ return {
11648
+ kind: "movement",
11649
+ movementId: row.id,
11650
+ viewer: scope.actor,
11651
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
11652
+ evidence: {
11653
+ chainId: row.chainId,
11654
+ contractAddress: row.token,
11655
+ tokenAddress: row.token,
11656
+ transactionHash: row.txHash,
11657
+ blockNumber,
11658
+ blockHash: row.blockHash,
11659
+ inclusion: "included",
11660
+ verification: "verified",
11661
+ canonicalState: "finalized",
11662
+ transferLogIndex: row.logIndex,
11663
+ source: row.from,
11664
+ destination: row.to,
11665
+ rawAmount: row.amount.toString(),
11666
+ link: {
11667
+ movementId: row.id,
11668
+ depositPaymentId: null
11669
+ }
11670
+ }
11671
+ };
11672
+ });
11463
11673
  }
11464
- /**
11465
- * Tell the caller when the indexed page for this Safe changed.
11466
- *
11467
- * The transport is the indexer's one SSE connection, wired in the Ponder
11468
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
11469
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
11470
- * the caller re-reads.
11471
- */
11472
11674
  function subscribeActivity(deps, actor, controls, onChange) {
11473
11675
  return Effect.gen(function* () {
11474
11676
  const scope = yield* overlay(deps, actor, controls, {});
11475
11677
  if (scope.safe === null) return () => {};
11476
- const page = {
11477
- safe: scope.safe,
11478
- limit: INTENT_WINDOW
11479
- };
11480
11678
  let delivered = false;
11481
- const notify = () => {
11482
- if (delivered) onChange();
11483
- delivered = true;
11484
- };
11485
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
11486
- if (snapshot.status === "ok") notify();
11679
+ return yield* deps.indexer.subscribe("transfer", {
11680
+ safe: toAddress(scope.safe),
11681
+ limit: BATCH
11682
+ }, (snapshot) => {
11683
+ if (snapshot.status === "ok") {
11684
+ if (delivered) onChange();
11685
+ delivered = true;
11686
+ }
11487
11687
  if (snapshot.status === "error") {
11488
11688
  delivered = true;
11489
11689
  onChange();
@@ -11493,7 +11693,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
11493
11693
  }
11494
11694
  //#endregion
11495
11695
  //#region ../../packages/sdk/package.json
11496
- var version$1 = "4.20.0-beta.1";
11696
+ var version$1 = "4.20.0-beta.2";
11497
11697
  //#endregion
11498
11698
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
11499
11699
  /** Fixed fallback for failures that have no safe message. */
@@ -13059,7 +13259,10 @@ function makeFinancialOpsMethods(deps) {
13059
13259
  ...params?.limit === void 0 ? {} : { limit: params.limit },
13060
13260
  ...params?.range === void 0 ? {} : { range: params.range },
13061
13261
  ...params?.filter === void 0 ? {} : { filter: params.filter }
13062
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13262
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
13263
+ ...page,
13264
+ items: page.items.filter(isActivityVisible)
13265
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
13063
13266
  },
13064
13267
  summary: (params, options) => {
13065
13268
  const controls = deps.invocationControls?.(options) ?? options;
@@ -13080,7 +13283,10 @@ function makeFinancialOpsMethods(deps) {
13080
13283
  actor: scope,
13081
13284
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
13082
13285
  });
13083
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
13286
+ }).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" ? {
13287
+ kind: "organization",
13288
+ orgId: actor.orgId
13289
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
13084
13290
  input: {
13085
13291
  kind: reference.kind,
13086
13292
  id: reference.id,
@@ -47668,7 +47874,8 @@ function decodeTransfer(value) {
47668
47874
  token: toAddress(hex(row, "token")),
47669
47875
  from: toAddress(hex(row, "from")),
47670
47876
  to: toAddress(hex(row, "to")),
47671
- amount: big(row, "amount")
47877
+ amount: big(row, "amount"),
47878
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
47672
47879
  };
47673
47880
  }
47674
47881
  function decodePayment(value) {
@@ -47754,6 +47961,60 @@ const ACTOR_PARAMS = {
47754
47961
  */
47755
47962
  function ponderQuery(table, input) {
47756
47963
  const safe = assertIndexerPage(input);
47964
+ if (input.eventId !== void 0) {
47965
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
47966
+ return {
47967
+ 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`,
47968
+ params: [
47969
+ safe,
47970
+ safe,
47971
+ input.eventId,
47972
+ 1
47973
+ ],
47974
+ typings: [
47975
+ "none",
47976
+ "none",
47977
+ "none",
47978
+ "none"
47979
+ ]
47980
+ };
47981
+ }
47982
+ if (input.settlements !== void 0) {
47983
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
47984
+ const params = [
47985
+ safe,
47986
+ safe,
47987
+ ...input.settlements,
47988
+ input.limit
47989
+ ];
47990
+ return {
47991
+ 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}`,
47992
+ params,
47993
+ typings: params.map(() => "none")
47994
+ };
47995
+ }
47996
+ if (input.timeline !== void 0) {
47997
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
47998
+ const { from, to, before } = input.timeline;
47999
+ const params = [
48000
+ safe,
48001
+ safe,
48002
+ from,
48003
+ to
48004
+ ];
48005
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
48006
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
48007
+ if (before !== void 0) {
48008
+ params.push(before.timestamp, before.timestamp, before.id);
48009
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
48010
+ }
48011
+ params.push(input.limit);
48012
+ return {
48013
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
48014
+ params,
48015
+ typings: params.map(() => "none")
48016
+ };
48017
+ }
47757
48018
  const offset = input.offset ?? 0;
47758
48019
  const actors = ACTOR_PARAMS[table];
47759
48020
  const params = [];
@@ -49496,8 +49757,8 @@ function renderResult(mode, envelope, lines = []) {
49496
49757
  }
49497
49758
  //#endregion
49498
49759
  //#region src/version.ts
49499
- const cliVersion = "4.20.0-beta.1";
49500
- const sdkVersion = "4.20.0-beta.1";
49760
+ const cliVersion = "4.20.0-beta.2";
49761
+ const sdkVersion = "4.20.0-beta.2";
49501
49762
  //#endregion
49502
49763
  //#region src/runtime/run-command.ts
49503
49764
  var CliRuntime = class extends Context.Service()("capxul-cli/Runtime") {};