@capxul/mcp 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.
Files changed (2) hide show
  1. package/dist/main.mjs +457 -196
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -12609,6 +12609,9 @@ const moneyExecutionContract = {
12609
12609
  //#region ../../packages/sdk/src/ports/indexer-read.ts
12610
12610
  /** The indexer's own page bound. Exceeding it is a caller defect, not a refusal. */
12611
12611
  const INDEXER_MAX_LIMIT = 1e3;
12612
+ function validTime(value) {
12613
+ return Number.isSafeInteger(value) && value >= 0 && value <= Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
12614
+ }
12612
12615
  const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12613
12616
  /**
12614
12617
  * Refuse a page the indexer would refuse, before any implementation spends a
@@ -12616,12 +12619,21 @@ const LOWER_CASE_ADDRESS = /^0x[0-9a-f]{40}$/;
12616
12619
  * against the hermetic adapter cannot pass on input production rejects. Throws
12617
12620
  * a `CapxulError`; callers convert it to an `IndexerReadError`.
12618
12621
  */
12622
+ function assertEventLookup(input, id) {
12623
+ 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");
12624
+ }
12619
12625
  function assertIndexerPage(input) {
12620
12626
  const safe = String(input.safe).toLowerCase();
12621
12627
  if (!LOWER_CASE_ADDRESS.test(safe)) throw Errors.invalidInput("safe", "must be a lower-case EVM address");
12622
12628
  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}`);
12623
12629
  const offset = input.offset ?? 0;
12624
12630
  if (!Number.isSafeInteger(offset) || offset < 0) throw Errors.invalidInput("offset", "must be a non-negative integer");
12631
+ const timeline = input.timeline;
12632
+ if (input.eventId !== void 0) assertEventLookup(input, input.eventId);
12633
+ 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");
12634
+ if (timeline !== void 0) {
12635
+ 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");
12636
+ }
12625
12637
  return safe;
12626
12638
  }
12627
12639
  var IndexerReadError = class extends Data.TaggedError("IndexerReadError") {};
@@ -12651,20 +12663,6 @@ function directionFor(safe, transfer) {
12651
12663
  if (from === safe && to === safe) return "self";
12652
12664
  return to === safe ? "in" : "out";
12653
12665
  }
12654
- /**
12655
- * The Transfer a protocol Payment moved is the same money as its `payment`
12656
- * row. One actor sees one item for it, so the Transfer is claimed and does not
12657
- * become a second row. Another transfer in the same transaction that this
12658
- * payment did not move stays unclaimed and keeps its own row.
12659
- */
12660
- function claimedTransferIds(transfers, payments) {
12661
- const claimed = /* @__PURE__ */ new Set();
12662
- for (const payment of payments) {
12663
- 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());
12664
- if (match !== void 0) claimed.add(match.id);
12665
- }
12666
- return claimed;
12667
- }
12668
12666
  function withAnnotation(item, annotation) {
12669
12667
  return annotation?.counterpartyLabel === void 0 ? item : {
12670
12668
  ...item,
@@ -12675,10 +12673,14 @@ function withAnnotation(item, annotation) {
12675
12673
  * An indexed settlement confirms the intent. A missing row can be outside
12676
12674
  * this page or still backfilling; only the correction owner can prove rollback.
12677
12675
  */
12678
- function confirmPayment(intent, chain) {
12679
- if (chain !== void 0) return {
12676
+ function confirmPayment(intent, chain, expected) {
12677
+ if (chain !== void 0 && matchesPaymentFact(chain, expected)) return {
12680
12678
  ...intent,
12681
12679
  status: "settled",
12680
+ ...intent.execution === void 0 ? {} : { execution: {
12681
+ status: "settled",
12682
+ retryRequired: false
12683
+ } },
12682
12684
  receipt: {
12683
12685
  chainId: chain.chainId,
12684
12686
  transactionHash: chain.txHash
@@ -12687,16 +12689,22 @@ function confirmPayment(intent, chain) {
12687
12689
  };
12688
12690
  return intent;
12689
12691
  }
12692
+ function matchesPaymentFact(chain, expected) {
12693
+ 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;
12694
+ }
12690
12695
  /**
12691
12696
  * One unclaimed Transfer as an Activity row, or `null` when this SDK cannot
12692
12697
  * denominate its asset. The indexer serves only configured assets, so a row
12693
12698
  * the registry does not know means the SDK and that deployment disagree; it is
12694
12699
  * dropped rather than shown with a guessed currency.
12695
12700
  */
12701
+ function isActivityVisible(item) {
12702
+ return !(item.kind === "payment" && item.direction === "in" && item.execution?.notSent);
12703
+ }
12696
12704
  function transferItem(safe, transfer, annotation) {
12697
12705
  const asset = configuredMoneyAssetById(transfer.assetId);
12698
12706
  if (asset === null) return null;
12699
- const direction = directionFor(safe, transfer);
12707
+ const direction = directionFor(safe.toLowerCase(), transfer);
12700
12708
  const counterparty = direction === "in" ? transfer.from : transfer.to;
12701
12709
  const at = blockMillis(transfer);
12702
12710
  return withAnnotation({
@@ -12718,47 +12726,6 @@ function transferItem(safe, transfer, annotation) {
12718
12726
  updatedAt: at
12719
12727
  }, annotation);
12720
12728
  }
12721
- /** Newest first. A Payment precedes a Movement at one instant, as the ledger reader orders them. */
12722
- function compareItems(left, right) {
12723
- if (right.createdAt !== left.createdAt) return right.createdAt - left.createdAt;
12724
- if (left.kind !== right.kind) return left.kind === "payment" ? -1 : 1;
12725
- if (left.id === right.id) return 0;
12726
- return left.id < right.id ? 1 : -1;
12727
- }
12728
- function stitchActivity(input) {
12729
- const safe = input.safe.toLowerCase();
12730
- const chainBySettlement = /* @__PURE__ */ new Map();
12731
- for (const row of input.payments) chainBySettlement.set(row.settlementId.toLowerCase(), row);
12732
- const chainByPaymentId = /* @__PURE__ */ new Map();
12733
- for (const link of input.links) {
12734
- const row = chainBySettlement.get(link.settlementId.toLowerCase());
12735
- if (row !== void 0) chainByPaymentId.set(link.paymentId, row);
12736
- }
12737
- const annotations = /* @__PURE__ */ new Map();
12738
- for (const annotation of input.annotations) annotations.set(`${annotation.reference.kind}:${annotation.reference.id}`, annotation);
12739
- const items = [];
12740
- const claiming = [];
12741
- for (const intent of input.intents) {
12742
- if (intent.kind !== "payment") continue;
12743
- const chain = chainByPaymentId.get(intent.id);
12744
- if (chain !== void 0) claiming.push(chain);
12745
- items.push(withAnnotation(confirmPayment(intent, chain), annotations.get(`payment:${intent.id}`)));
12746
- }
12747
- const claimed = claimedTransferIds(input.transfers, claiming);
12748
- for (const transfer of input.transfers) {
12749
- if (claimed.has(transfer.id)) continue;
12750
- const item = transferItem(safe, transfer, annotations.get(`movement:${transfer.id}`));
12751
- if (item !== null) items.push(item);
12752
- }
12753
- return items.sort(compareItems);
12754
- }
12755
- /** The reference ids one stitched page needs its Convex overlay for. */
12756
- function overlayReferences(items) {
12757
- return items.map((item) => ({
12758
- kind: item.kind,
12759
- id: item.id
12760
- }));
12761
- }
12762
12729
  /**
12763
12730
  * Money in, money out and the pending share, one bucket per currency and
12764
12731
  * precision. Two assets that peg to the same currency at different precision
@@ -12777,7 +12744,7 @@ function summarizeItems(items) {
12777
12744
  };
12778
12745
  const raw = rawUnits(item.amount.value, item.amount.decimals);
12779
12746
  if (item.kind === "payment" && item.status !== "settled") {
12780
- if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed") bucket.pendingIn += raw;
12747
+ if (item.direction === "in" && item.status !== "cancelled" && item.status !== "failed" && !item.execution?.notSent) bucket.pendingIn += raw;
12781
12748
  } else if (item.direction === "in") bucket.in += raw;
12782
12749
  else if (item.direction === "out") bucket.out += raw;
12783
12750
  buckets.set(key, bucket);
@@ -12800,179 +12767,412 @@ function rawUnits(value, decimals) {
12800
12767
  }
12801
12768
  //#endregion
12802
12769
  //#region ../../packages/sdk/src/surface/activity-read.ts
12803
- /** How many Convex intent rows one page reads for its labels and pending rows. */
12804
- const INTENT_WINDOW = 100;
12805
- /** movement/annotations:overlay bounds each identifier array to 200. */
12770
+ const BATCH = 100;
12806
12771
  const MAX_OVERLAY_IDS = 200;
12807
- /** The window a summary aggregates over. The indexer's own page bound. */
12808
- const SUMMARY_WINDOW = INDEXER_MAX_LIMIT;
12809
- function parseCursor(value) {
12810
- if (value === void 0) return 0;
12811
- try {
12812
- const parsed = JSON.parse(value);
12813
- return Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? parsed.offset : 0;
12814
- } catch {
12815
- return 0;
12816
- }
12772
+ function actorKey(actor) {
12773
+ return actor.kind === "account" ? `account:${actor.accountId}` : `organization:${actor.orgId}`;
12774
+ }
12775
+ function scopeKey(scope) {
12776
+ return `${scope.principalAccountId ?? "legacy"}|${actorKey(scope.actor)}|${scope.safe?.toLowerCase() ?? "none"}`;
12777
+ }
12778
+ function selectionKey(params) {
12779
+ const filter = params?.filter;
12780
+ return JSON.stringify([
12781
+ params?.range?.from ?? null,
12782
+ params?.range?.to ?? null,
12783
+ filter?.kind ?? null,
12784
+ filter?.direction ?? null,
12785
+ filter?.status === void 0 ? null : [...new Set(filter.status)].sort()
12786
+ ]);
12817
12787
  }
12818
- function inRange(item, range) {
12819
- if (range?.from !== void 0 && item.createdAt < range.from) return false;
12820
- return range?.to === void 0 || item.createdAt <= range.to;
12788
+ function validString(value, max) {
12789
+ return typeof value === "string" && value.length <= max;
12790
+ }
12791
+ function validPosition(position) {
12792
+ 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);
12793
+ }
12794
+ function parseCursor(value, scope, selection) {
12795
+ if (value === void 0) return null;
12796
+ if (value.length > 4e4) throw Errors.invalidInput("cursor", "invalid Activity cursor");
12797
+ const cursor = JSON.parse(value);
12798
+ 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");
12799
+ return cursor;
12800
+ }
12801
+ function requireParams(params) {
12802
+ const limit = params?.limit ?? 25;
12803
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > BATCH) throw Errors.invalidInput("limit", "must be an integer from 1 to 100");
12804
+ 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");
12805
+ const f = params?.filter;
12806
+ if (f?.kind !== void 0 && f.kind !== "payment" && f.kind !== "movement" || f?.direction !== void 0 && ![
12807
+ "in",
12808
+ "out",
12809
+ "self"
12810
+ ].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");
12821
12811
  }
12822
- /** The caller's filter applied to the merged page. The join owns the order. */
12823
12812
  function matchesFilter(item, filter) {
12824
- if (filter === void 0) return true;
12825
- if (filter.kind !== void 0 && item.kind !== filter.kind) return false;
12826
- if (filter.direction !== void 0 && item.direction !== filter.direction) return false;
12827
- if (filter.status === void 0) return true;
12828
- return item.kind === "payment" && filter.status.includes(item.status);
12813
+ 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));
12829
12814
  }
12830
12815
  function overlay(deps, actor, controls, input) {
12831
12816
  const count = Math.max(input.references?.length ?? 0, input.settlementIds?.length ?? 0);
12832
- const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, index) => index * MAX_OVERLAY_IDS);
12817
+ const offsets = Array.from({ length: Math.max(1, Math.ceil(count / MAX_OVERLAY_IDS)) }, (_, i) => i * MAX_OVERLAY_IDS);
12833
12818
  return Effect.forEach(offsets, (offset) => deps.convexCall.query(deps.functions.activityOverlay, copyInvocationObservation(controls, { input: {
12834
12819
  ...actor === void 0 ? {} : { actor },
12835
12820
  ...input.references === void 0 ? {} : { references: input.references.slice(offset, offset + MAX_OVERLAY_IDS) },
12836
12821
  ...input.settlementIds === void 0 ? {} : { settlementIds: input.settlementIds.slice(offset, offset + MAX_OVERLAY_IDS) }
12837
- } })), { concurrency: 2 }).pipe(Effect.map((pages) => ({
12838
- ...pages[0],
12839
- links: pages.flatMap((page) => page.links),
12840
- annotations: pages.flatMap((page) => page.annotations)
12841
- })));
12822
+ } })), { concurrency: 2 }).pipe(Effect.flatMap((pages) => {
12823
+ const first = pages[0];
12824
+ if (pages.some((page) => scopeKey(page) !== scopeKey(first))) return Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
12825
+ return Effect.succeed({
12826
+ ...first,
12827
+ links: pages.flatMap((page) => page.links),
12828
+ annotations: pages.flatMap((page) => page.annotations),
12829
+ ...pages.every((page) => page.payments !== void 0) ? { payments: pages.flatMap((page) => page.payments ?? []) } : {}
12830
+ });
12831
+ }));
12842
12832
  }
12843
12833
  function convexPage(deps, actor, controls, input) {
12844
12834
  return deps.convexCall.query(deps.functions.activityList, copyInvocationObservation(controls, { input: {
12845
- ...actor === void 0 ? {} : { actor },
12846
- ...input.cursor === void 0 ? {} : { cursor: input.cursor },
12847
- ...input.limit === void 0 ? {} : { limit: input.limit },
12848
- ...input.range === void 0 ? {} : { range: input.range }
12835
+ ...input,
12836
+ actor
12849
12837
  } }));
12850
12838
  }
12851
- /** Both indexed tables for one Safe, or `null` when the indexer cannot answer. */
12852
- function chainRows(deps, safe, limit) {
12853
- const page = {
12854
- safe,
12855
- limit: Math.min(limit, INDEXER_MAX_LIMIT)
12856
- };
12857
- return Effect.all([deps.indexer.read("transfer", page), deps.indexer.read("payment", page)], { concurrency: 2 }).pipe(Effect.map(([transfers, payments]) => ({
12858
- transfers,
12859
- payments
12860
- })), Effect.catch(() => Effect.succeed(null)));
12839
+ function chainRead(deps, table, input) {
12840
+ return deps.indexer.read(table, input).pipe(Effect.catch((error) => {
12841
+ if (error.publicError.details?.reason === "scope-refused" || ![
12842
+ "NETWORK_ERROR",
12843
+ "RATE_LIMITED",
12844
+ "PROVIDER_ERROR",
12845
+ "CAPABILITY_UNAVAILABLE",
12846
+ "TIMEOUT"
12847
+ ].includes(error.publicCode)) return Effect.fail({ publicError: error.publicError });
12848
+ return Effect.succeed(null);
12849
+ }));
12861
12850
  }
12862
- /** The stitched, filtered rows of one window, newest first. */
12863
- function stitchedWindow(deps, actor, controls, window, params) {
12851
+ function fallbackPage(deps, scope, controls, params, state, degraded) {
12864
12852
  return Effect.gen(function* () {
12865
- const scope = yield* overlay(deps, actor, controls, {});
12866
- const convex = yield* convexPage(deps, actor, controls, {
12867
- limit: INTENT_WINDOW,
12868
- ...params.range === void 0 ? {} : { range: params.range }
12853
+ const page = yield* convexPage(deps, scope.actor, controls, {
12854
+ limit: params?.limit ?? 25,
12855
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor },
12856
+ range: {
12857
+ ...params?.range?.from === void 0 ? {} : { from: params.range.from },
12858
+ to: Math.min(state.checkpoint, params?.range?.to ?? state.checkpoint)
12859
+ },
12860
+ ...params?.filter === void 0 ? {} : { filter: params.filter }
12869
12861
  });
12870
- const rows = scope.safe === null ? null : yield* chainRows(deps, scope.safe, window);
12871
- if (rows === null || scope.safe === null) {
12872
- if (scope.safe !== null) yield* Effect.annotateCurrentSpan({
12873
- read_source: "convex",
12874
- read_warning: "indexer-unavailable"
12875
- });
12876
- return {
12877
- items: convex.items.filter((item) => matchesFilter(item, params.filter)),
12878
- stitched: false,
12879
- ...scope.safe === null ? {} : { readWarning: "indexer-unavailable" }
12880
- };
12862
+ const decoration = yield* overlay(deps, scope.actor, controls, { references: page.items.map((item) => ({
12863
+ kind: item.kind,
12864
+ id: item.id
12865
+ })) });
12866
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during fallback") });
12867
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
12868
+ yield* Effect.annotateCurrentSpan({
12869
+ read_source: "convex",
12870
+ ...degraded ? { read_warning: "indexer-unavailable" } : {}
12871
+ });
12872
+ return {
12873
+ ...page,
12874
+ items: page.items.filter(isActivityVisible).map((item) => withAnnotation(item, annotations.get(`${item.kind}:${item.id}`))),
12875
+ checkpoint: state.checkpoint,
12876
+ cursor: page.cursor === null ? null : JSON.stringify({
12877
+ ...state,
12878
+ source: "fallback",
12879
+ paymentCursor: page.cursor,
12880
+ degraded
12881
+ }),
12882
+ ...degraded ? { readWarning: "indexer-unavailable" } : {}
12883
+ };
12884
+ });
12885
+ }
12886
+ function boundedRange(range, checkpoint) {
12887
+ return {
12888
+ ...range?.from === void 0 ? {} : { from: range.from },
12889
+ to: Math.min(checkpoint, range?.to ?? checkpoint)
12890
+ };
12891
+ }
12892
+ function unavailablePage(deps, scope, controls, params, state, first) {
12893
+ if (first) return fallbackPage(deps, scope, controls, params, state, true);
12894
+ return Effect.annotateCurrentSpan({
12895
+ read_source: "indexer",
12896
+ read_warning: "indexer-unavailable"
12897
+ }).pipe(Effect.as({
12898
+ items: [],
12899
+ cursor: null,
12900
+ checkpoint: state.checkpoint,
12901
+ observedAt: Date.now(),
12902
+ readWarning: "indexer-unavailable"
12903
+ }));
12904
+ }
12905
+ function readTransfers(deps, safe, range, state, wanted, first) {
12906
+ const from = Math.max(0, Math.ceil((range.from ?? 0) / 1e3));
12907
+ const to = Math.floor(range.to / 1e3);
12908
+ if (state.transferDone || from > to || !wanted && !first) return Effect.succeed([]);
12909
+ return chainRead(deps, "transfer", {
12910
+ safe: toAddress(safe),
12911
+ limit: wanted ? BATCH : 1,
12912
+ timeline: {
12913
+ from,
12914
+ to,
12915
+ ...state.transferBefore === null ? {} : { before: state.transferBefore }
12881
12916
  }
12882
- yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
12883
- const decoration = yield* overlay(deps, actor, controls, {
12884
- references: [...overlayReferences(convex.items), ...rows.transfers.map((row) => ({
12917
+ });
12918
+ }
12919
+ function readPayments(deps, scope, controls, range, state, wanted) {
12920
+ const done = state.paymentDone || !wanted;
12921
+ if (state.paymentPending.length > 0 || done) return Effect.succeed({
12922
+ items: [],
12923
+ pending: state.paymentPending,
12924
+ cursor: state.paymentCursor,
12925
+ done
12926
+ });
12927
+ return convexPage(deps, scope.actor, controls, {
12928
+ limit: BATCH,
12929
+ range,
12930
+ filter: { kind: "payment" },
12931
+ ...state.paymentCursor === null ? {} : { cursor: state.paymentCursor }
12932
+ }).pipe(Effect.map((page) => ({
12933
+ items: page.items,
12934
+ pending: page.items.map((item) => item.id),
12935
+ cursor: page.cursor,
12936
+ done: page.cursor === null
12937
+ })));
12938
+ }
12939
+ function readPaymentFacts(deps, safe, settlements) {
12940
+ const offsets = Array.from({ length: Math.ceil(settlements.length / 50) }, (_, i) => i * 50);
12941
+ return Effect.forEach(offsets, (offset) => chainRead(deps, "payment", {
12942
+ safe: toAddress(safe),
12943
+ limit: 50,
12944
+ settlements: settlements.slice(offset, offset + 50)
12945
+ }), { concurrency: 2 }).pipe(Effect.map((pages) => pages.some((page) => page === null) ? null : pages.flatMap((page) => page ?? [])));
12946
+ }
12947
+ function decoratePage(deps, scope, controls, transfers, page, range) {
12948
+ return Effect.gen(function* () {
12949
+ const decoration = yield* overlay(deps, scope.actor, controls, {
12950
+ references: [...page.pending.map((id) => ({
12951
+ kind: "payment",
12952
+ id
12953
+ })), ...transfers.map((row) => ({
12885
12954
  kind: "movement",
12886
12955
  id: row.id
12887
12956
  }))],
12888
- settlementIds: rows.payments.map((row) => row.settlementId)
12957
+ settlementIds: [...new Set(transfers.flatMap((row) => row.paymentSettlementId ? [row.paymentSettlementId] : []))]
12889
12958
  });
12890
- const intents = [...convex.items];
12891
- const missing = new Set(decoration.links.map((link) => link.paymentId));
12892
- for (const item of intents) if (item.kind === "payment") missing.delete(item.id);
12893
- let cursor = convex.cursor;
12894
- while (missing.size > 0 && cursor !== null) {
12895
- const page = yield* convexPage(deps, actor, controls, {
12896
- limit: INTENT_WINDOW,
12897
- cursor,
12898
- ...params.range === void 0 ? {} : { range: params.range }
12899
- });
12900
- for (const item of page.items) if (item.kind === "payment" && missing.delete(item.id)) intents.push(item);
12901
- cursor = page.cursor;
12959
+ if (scopeKey(decoration) !== scopeKey(scope)) return yield* Effect.fail({ publicError: Errors.invalidInput("actor", "Activity scope changed during the read") });
12960
+ const byId = new Map([...page.items, ...decoration.payments ?? []].map((item) => [item.id, item]));
12961
+ const settlements = [...new Set(decoration.links.map((link) => link.settlementId))];
12962
+ const facts = yield* readPaymentFacts(deps, scope.safe, settlements);
12963
+ if (facts === null) return null;
12964
+ const bySettlement = new Map(facts.map((row) => [row.settlementId, row]));
12965
+ const byPayment = new Map(decoration.links.map((link) => [link.paymentId, {
12966
+ fact: bySettlement.get(link.settlementId),
12967
+ expected: link.expected
12968
+ }]));
12969
+ const annotations = new Map(decoration.annotations.map((annotation) => [`${annotation.reference.kind}:${annotation.reference.id}`, annotation]));
12970
+ return {
12971
+ payments: page.pending.flatMap((id) => {
12972
+ const item = byId.get(id);
12973
+ if (item === void 0 || item.kind !== "payment" || item.createdAt > range.to || range.from !== void 0 && item.createdAt < range.from) return [];
12974
+ const confirmed = withAnnotation(confirmPayment(item, byPayment.get(id)?.fact, byPayment.get(id)?.expected), annotations.get(`payment:${id}`));
12975
+ return isActivityVisible(confirmed) ? [confirmed] : [];
12976
+ }),
12977
+ annotations,
12978
+ linked: new Set(decoration.links.filter((link) => matchesPaymentFact(bySettlement.get(link.settlementId), link.expected)).map((link) => link.settlementId))
12979
+ };
12980
+ });
12981
+ }
12982
+ function mergePage(safe, filter, limit, state, page, decoration, transfers, wantsTransfers) {
12983
+ const { payments, annotations, linked } = decoration;
12984
+ let pi = 0;
12985
+ let ti = 0;
12986
+ const items = [];
12987
+ const transferEnd = !wantsTransfers || transfers.length < BATCH;
12988
+ let transferBefore = state.transferBefore;
12989
+ while (items.length < limit) {
12990
+ if (pi === payments.length && !page.done || ti === transfers.length && !transferEnd) break;
12991
+ const payment = payments[pi];
12992
+ const row = transfers[ti];
12993
+ if (payment === void 0 && row === void 0) break;
12994
+ if (payment !== void 0 && (row === void 0 || payment.createdAt >= Number(row.timestamp) * 1e3)) {
12995
+ pi += 1;
12996
+ if (matchesFilter(payment, filter)) items.push(payment);
12997
+ } else if (row !== void 0) {
12998
+ ti += 1;
12999
+ transferBefore = {
13000
+ timestamp: Number(row.timestamp),
13001
+ id: row.id
13002
+ };
13003
+ if (row.paymentSettlementId && linked.has(row.paymentSettlementId)) continue;
13004
+ const item = transferItem(safe, row, annotations.get(`movement:${row.id}`));
13005
+ if (item !== null && matchesFilter(item, filter)) items.push(item);
13006
+ }
13007
+ }
13008
+ return {
13009
+ items,
13010
+ next: {
13011
+ ...state,
13012
+ paymentCursor: page.cursor,
13013
+ paymentDone: page.done,
13014
+ paymentPending: payments.slice(pi).map((item) => item.id),
13015
+ transferBefore,
13016
+ transferDone: ti === transfers.length && transferEnd
12902
13017
  }
12903
- const older = intents.slice(convex.items.length);
12904
- const olderDecoration = older.length === 0 ? [] : (yield* overlay(deps, actor, controls, { references: overlayReferences(older) })).annotations;
13018
+ };
13019
+ }
13020
+ function indexedPage(deps, scope, controls, params, state, first) {
13021
+ return Effect.gen(function* () {
13022
+ const range = boundedRange(params?.range, state.checkpoint);
13023
+ const wantsTransfers = params?.filter?.kind !== "payment" && params?.filter?.status === void 0;
13024
+ const found = yield* readTransfers(deps, scope.safe, range, state, wantsTransfers, first);
13025
+ if (found === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
13026
+ const transfers = wantsTransfers ? found : [];
13027
+ const page = yield* readPayments(deps, scope, controls, range, state, params?.filter?.kind !== "movement");
13028
+ const decoration = yield* decoratePage(deps, scope, controls, transfers, page, range);
13029
+ if (decoration === null) return yield* unavailablePage(deps, scope, controls, params, state, first);
13030
+ const { items, next } = mergePage(scope.safe, params?.filter, params?.limit ?? 25, state, page, decoration, transfers, wantsTransfers);
13031
+ const cursor = next.paymentDone && next.paymentPending.length === 0 && next.transferDone ? null : JSON.stringify(next);
13032
+ if (cursor !== null && cursor === params?.cursor) return yield* Effect.fail({ publicError: Errors.invalidInput("cursor", "Activity source did not advance") });
13033
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
12905
13034
  return {
12906
- items: stitchActivity({
12907
- safe: scope.safe,
12908
- transfers: rows.transfers,
12909
- payments: rows.payments,
12910
- intents,
12911
- links: decoration.links,
12912
- annotations: [...decoration.annotations, ...olderDecoration]
12913
- }).filter((item) => inRange(item, params.range) && matchesFilter(item, params.filter)),
12914
- stitched: true
13035
+ items,
13036
+ cursor,
13037
+ checkpoint: state.checkpoint,
13038
+ observedAt: Date.now()
12915
13039
  };
12916
13040
  });
12917
13041
  }
12918
13042
  function readActivityPage(deps, actor, controls, params) {
12919
- const limit = params?.limit ?? 25;
12920
- const offset = parseCursor(params?.cursor);
12921
- return stitchedWindow(deps, actor, controls, offset + limit + 1, {
12922
- ...params?.range === void 0 ? {} : { range: params.range },
12923
- ...params?.filter === void 0 ? {} : { filter: params.filter }
12924
- }).pipe(Effect.map(({ items, stitched, readWarning }) => {
12925
- const observedAt = Date.now();
12926
- if (!stitched) return {
12927
- items: items.slice(0, limit),
13043
+ return Effect.gen(function* () {
13044
+ yield* Effect.try({
13045
+ try: () => requireParams(params),
13046
+ catch: (cause) => ({ publicError: cause })
13047
+ });
13048
+ const scope = yield* overlay(deps, actor, controls, {});
13049
+ const selection = selectionKey(params);
13050
+ const cursor = yield* Effect.try({
13051
+ try: () => parseCursor(params?.cursor, scopeKey(scope), selection),
13052
+ catch: () => ({ publicError: Errors.invalidInput("cursor", "invalid or obsolete Activity cursor; restart the read") })
13053
+ });
13054
+ const state = cursor ?? {
13055
+ version: 1,
13056
+ scope: scopeKey(scope),
13057
+ selection,
13058
+ checkpoint: Date.now(),
13059
+ source: "indexed",
13060
+ paymentCursor: null,
13061
+ paymentDone: false,
13062
+ paymentPending: [],
13063
+ transferBefore: null,
13064
+ transferDone: false,
13065
+ degraded: false
13066
+ };
13067
+ const range = boundedRange(params?.range, state.checkpoint);
13068
+ if (range.from !== void 0 && range.from > range.to) return {
13069
+ items: [],
12928
13070
  cursor: null,
12929
- checkpoint: observedAt,
12930
- observedAt,
12931
- ...readWarning === void 0 ? {} : { readWarning }
13071
+ checkpoint: state.checkpoint,
13072
+ observedAt: Date.now()
12932
13073
  };
13074
+ if (state.source === "fallback" || scope.safe === null) return yield* fallbackPage(deps, scope, controls, params, state, state.degraded);
13075
+ if (scope.payments === void 0) return yield* unavailablePage(deps, scope, controls, params, state, cursor === null);
13076
+ return yield* indexedPage(deps, scope, controls, params, state, cursor === null);
13077
+ });
13078
+ }
13079
+ function readActivitySummary(deps, actor, controls, params) {
13080
+ return Effect.gen(function* () {
13081
+ const totals = /* @__PURE__ */ new Map();
13082
+ let cursor;
13083
+ let count = 0;
13084
+ let readWarning;
13085
+ do {
13086
+ const page = yield* readActivityPage(deps, actor, controls, {
13087
+ limit: BATCH,
13088
+ ...cursor === void 0 ? {} : { cursor },
13089
+ ...params?.window === void 0 ? {} : { range: params.window }
13090
+ });
13091
+ count += page.items.length;
13092
+ readWarning ??= page.readWarning;
13093
+ for (const total of summarizeItems(page.items)) {
13094
+ const key = `${total.currency}:${total.decimals}`;
13095
+ const old = totals.get(key);
13096
+ totals.set(key, old === void 0 ? total : {
13097
+ ...total,
13098
+ in: String(BigInt(old.in) + BigInt(total.in)),
13099
+ out: String(BigInt(old.out) + BigInt(total.out)),
13100
+ net: String(BigInt(old.net) + BigInt(total.net)),
13101
+ pendingIn: String(BigInt(old.pendingIn) + BigInt(total.pendingIn))
13102
+ });
13103
+ }
13104
+ cursor = page.cursor ?? void 0;
13105
+ } while (cursor !== void 0);
12933
13106
  return {
12934
- items: items.slice(offset, offset + limit),
12935
- cursor: items.length > offset + limit ? JSON.stringify({ offset: offset + limit }) : null,
12936
- checkpoint: observedAt,
12937
- observedAt
13107
+ window: {
13108
+ from: params?.window?.from ?? null,
13109
+ to: params?.window?.to ?? null
13110
+ },
13111
+ totals: [...totals.values()],
13112
+ count,
13113
+ observedAt: Date.now(),
13114
+ ...readWarning === void 0 ? {} : { readWarning }
12938
13115
  };
12939
- }));
13116
+ });
12940
13117
  }
12941
- function readActivitySummary(deps, actor, controls, params) {
12942
- return stitchedWindow(deps, actor, controls, SUMMARY_WINDOW, params?.window === void 0 ? {} : { range: params.window }).pipe(Effect.map(({ items, readWarning }) => ({
12943
- window: {
12944
- from: params?.window?.from ?? null,
12945
- to: params?.window?.to ?? null
12946
- },
12947
- totals: summarizeItems(items),
12948
- count: items.length,
12949
- observedAt: Date.now(),
12950
- ...readWarning === void 0 ? {} : { readWarning }
12951
- })));
13118
+ /** An indexed Movement has no Convex materialization. Read its exact native row. */
13119
+ function readIndexedMovement(deps, actor, controls, eventId) {
13120
+ return Effect.gen(function* () {
13121
+ const scope = yield* overlay(deps, actor, controls, { references: [{
13122
+ kind: "movement",
13123
+ id: eventId
13124
+ }] });
13125
+ if (scope.safe === null) return null;
13126
+ const safe = scope.safe.toLowerCase();
13127
+ const row = (yield* deps.indexer.read("transfer", {
13128
+ safe: toAddress(safe),
13129
+ limit: 1,
13130
+ eventId
13131
+ }).pipe(Effect.catch((error) => Effect.fail({ publicError: error.publicError }))))[0];
13132
+ if (row === void 0) return null;
13133
+ const blockNumber = Number(row.blockNumber);
13134
+ 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") });
13135
+ yield* Effect.annotateCurrentSpan({ read_source: "indexer" });
13136
+ return {
13137
+ kind: "movement",
13138
+ movementId: row.id,
13139
+ viewer: scope.actor,
13140
+ annotation: scope.annotations.find((annotation) => annotation.reference.kind === "movement" && annotation.reference.id === row.id) ?? null,
13141
+ evidence: {
13142
+ chainId: row.chainId,
13143
+ contractAddress: row.token,
13144
+ tokenAddress: row.token,
13145
+ transactionHash: row.txHash,
13146
+ blockNumber,
13147
+ blockHash: row.blockHash,
13148
+ inclusion: "included",
13149
+ verification: "verified",
13150
+ canonicalState: "finalized",
13151
+ transferLogIndex: row.logIndex,
13152
+ source: row.from,
13153
+ destination: row.to,
13154
+ rawAmount: row.amount.toString(),
13155
+ link: {
13156
+ movementId: row.id,
13157
+ depositPaymentId: null
13158
+ }
13159
+ }
13160
+ };
13161
+ });
12952
13162
  }
12953
- /**
12954
- * Tell the caller when the indexed page for this Safe changed.
12955
- *
12956
- * The transport is the indexer's one SSE connection, wired in the Ponder
12957
- * adapter under `activity.subscribe`. A reactive snapshot is not a product
12958
- * event, so nothing here emits telemetry: the adapter reports a teardown, and
12959
- * the caller re-reads.
12960
- */
12961
13163
  function subscribeActivity(deps, actor, controls, onChange) {
12962
13164
  return Effect.gen(function* () {
12963
13165
  const scope = yield* overlay(deps, actor, controls, {});
12964
13166
  if (scope.safe === null) return () => {};
12965
- const page = {
12966
- safe: scope.safe,
12967
- limit: INTENT_WINDOW
12968
- };
12969
13167
  let delivered = false;
12970
- const notify = () => {
12971
- if (delivered) onChange();
12972
- delivered = true;
12973
- };
12974
- return yield* deps.indexer.subscribe("transfer", page, (snapshot) => {
12975
- if (snapshot.status === "ok") notify();
13168
+ return yield* deps.indexer.subscribe("transfer", {
13169
+ safe: toAddress(scope.safe),
13170
+ limit: BATCH
13171
+ }, (snapshot) => {
13172
+ if (snapshot.status === "ok") {
13173
+ if (delivered) onChange();
13174
+ delivered = true;
13175
+ }
12976
13176
  if (snapshot.status === "error") {
12977
13177
  delivered = true;
12978
13178
  onChange();
@@ -12982,7 +13182,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
12982
13182
  }
12983
13183
  //#endregion
12984
13184
  //#region ../../packages/sdk/package.json
12985
- var version$1 = "4.20.0-beta.1";
13185
+ var version$1 = "4.20.0-beta.2";
12986
13186
  //#endregion
12987
13187
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
12988
13188
  /** Fixed fallback for failures that have no safe message. */
@@ -14339,7 +14539,10 @@ function makeFinancialOpsMethods(deps) {
14339
14539
  ...params?.limit === void 0 ? {} : { limit: params.limit },
14340
14540
  ...params?.range === void 0 ? {} : { range: params.range },
14341
14541
  ...params?.filter === void 0 ? {} : { filter: params.filter }
14342
- } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14542
+ } })) : readActivityPage(indexerDeps, actor, controls, params)).pipe(Effect.map((page) => ({
14543
+ ...page,
14544
+ items: page.items.filter(isActivityVisible)
14545
+ })), Effect.tap(() => observeActivityScope(actor))))), controls, CAPXUL_OPERATIONS.activity.list, deps.runPromise);
14343
14546
  },
14344
14547
  summary: (params, options) => {
14345
14548
  const controls = deps.invocationControls?.(options) ?? options;
@@ -14360,7 +14563,10 @@ function makeFinancialOpsMethods(deps) {
14360
14563
  actor: scope,
14361
14564
  referenceActor: reference.kind === "payment" ? activityActorField(reference.actor).actor : void 0
14362
14565
  });
14363
- }).pipe(Effect.flatMap(({ actor, referenceActor }) => Effect.suspend(() => deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
14566
+ }).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" ? {
14567
+ kind: "organization",
14568
+ orgId: actor.orgId
14569
+ } : void 0, controls, reference.id) : deps.convexCall.query(fns.activityGet, copyInvocationObservation(controls, {
14364
14570
  input: {
14365
14571
  kind: reference.kind,
14366
14572
  id: reference.id,
@@ -47856,7 +48062,8 @@ function decodeTransfer(value) {
47856
48062
  token: toAddress(hex(row, "token")),
47857
48063
  from: toAddress(hex(row, "from")),
47858
48064
  to: toAddress(hex(row, "to")),
47859
- amount: big(row, "amount")
48065
+ amount: big(row, "amount"),
48066
+ ..."payment_settlement_id" in row ? { paymentSettlementId: row.payment_settlement_id === null ? null : hex(row, "payment_settlement_id") } : {}
47860
48067
  };
47861
48068
  }
47862
48069
  function decodePayment(value) {
@@ -47942,6 +48149,60 @@ const ACTOR_PARAMS = {
47942
48149
  */
47943
48150
  function ponderQuery(table, input) {
47944
48151
  const safe = assertIndexerPage(input);
48152
+ if (input.eventId !== void 0) {
48153
+ if (table !== "transfer") throw Errors.invalidInput("eventId", "only transfer reads accept a logical event ID");
48154
+ return {
48155
+ 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`,
48156
+ params: [
48157
+ safe,
48158
+ safe,
48159
+ input.eventId,
48160
+ 1
48161
+ ],
48162
+ typings: [
48163
+ "none",
48164
+ "none",
48165
+ "none",
48166
+ "none"
48167
+ ]
48168
+ };
48169
+ }
48170
+ if (input.settlements !== void 0) {
48171
+ if (table !== "payment") throw Errors.invalidInput("settlements", "only Payment reads accept settlement IDs");
48172
+ const params = [
48173
+ safe,
48174
+ safe,
48175
+ ...input.settlements,
48176
+ input.limit
48177
+ ];
48178
+ return {
48179
+ 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}`,
48180
+ params,
48181
+ typings: params.map(() => "none")
48182
+ };
48183
+ }
48184
+ if (input.timeline !== void 0) {
48185
+ if (table !== "transfer") throw Errors.invalidInput("timeline", "only transfer reads have a timeline");
48186
+ const { from, to, before } = input.timeline;
48187
+ const params = [
48188
+ safe,
48189
+ safe,
48190
+ from,
48191
+ to
48192
+ ];
48193
+ const select = HEADS.transfer.slice(0, HEADS.transfer.indexOf(" from \"transfer\"")) + ", \"payment_settlement_id\" from \"activity_transfer\" ";
48194
+ let where = "(\"activity_transfer\".\"from\" = $1 or \"activity_transfer\".\"to\" = $2) and \"activity_transfer\".\"timestamp\" >= $3 and \"activity_transfer\".\"timestamp\" <= $4";
48195
+ if (before !== void 0) {
48196
+ params.push(before.timestamp, before.timestamp, before.id);
48197
+ where += " and (\"activity_transfer\".\"timestamp\" < $5 or (\"activity_transfer\".\"timestamp\" = $6 and \"activity_transfer\".\"id\" < $7))";
48198
+ }
48199
+ params.push(input.limit);
48200
+ return {
48201
+ sql: `${select}where (${where}) order by "activity_transfer"."timestamp" desc, "activity_transfer"."id" desc limit $${params.length}`,
48202
+ params,
48203
+ typings: params.map(() => "none")
48204
+ };
48205
+ }
47945
48206
  const offset = input.offset ?? 0;
47946
48207
  const actors = ACTOR_PARAMS[table];
47947
48208
  const params = [];
@@ -49616,7 +49877,7 @@ function stdioIdentity(env = process.env, options = {}) {
49616
49877
  }
49617
49878
  //#endregion
49618
49879
  //#region package.json
49619
- var version = "4.20.0-beta.1";
49880
+ var version = "4.20.0-beta.2";
49620
49881
  //#endregion
49621
49882
  //#region src/deps.ts
49622
49883
  const NO_IDENTITY = async () => void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/mcp",
3
- "version": "4.20.0-beta.1",
3
+ "version": "4.20.0-beta.2",
4
4
  "private": false,
5
5
  "description": "Capxul MCP server for auth, Account readiness, identity, Organizations, relationships, requests, destinations, and testnet funding over Streamable HTTP.",
6
6
  "keywords": [
@@ -51,7 +51,7 @@
51
51
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
52
52
  "vite-plus": "0.3.0",
53
53
  "@capxul/config": "0.3.0",
54
- "@capxul/sdk": "4.20.0-beta.1",
54
+ "@capxul/sdk": "4.20.0-beta.2",
55
55
  "@capxul/typescript-config": "0.0.0"
56
56
  },
57
57
  "scripts": {