@mnemopay/sdk 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.createSandboxServer = exports.DEFAULT_ANOMALY_CONFIG = exports.CanarySystem = exports.BehaviorMonitor = exports.EWMADetector = exports.DEFAULT_BEHAVIORAL_CONFIG = exports.BehavioralEngine = exports.MerkleTree = exports.DEFAULT_FICO_CONFIG = exports.AgentFICO = exports.AgentCreditScore = exports.DEFAULT_ADAPTIVE_CONFIG = exports.AdaptiveEngine = exports.validateProfile = exports.loadProfileFromEnv = exports.GenericCheckoutStrategy = exports.ShopifyCheckoutStrategy = exports.CheckoutExecutor = exports.MockCommerceProvider = exports.CommerceEngine = exports.MnemoPayNetwork = exports.constantTimeEqual = exports.IdentityRegistry = exports.hashEntry = exports.Ledger = exports.JSONFileStorage = exports.SQLiteStorage = exports.NIGERIAN_BANKS = exports.PaystackRail = exports.LightningRail = exports.StripeRail = exports.MockRail = exports.BehaviorProfile = exports.TransactionGraph = exports.IsolationForest = exports.DEFAULT_RATE_LIMIT = exports.DEFAULT_FRAUD_CONFIG = exports.RateLimiter = exports.FraudGuard = exports.l2Normalize = exports.localEmbed = exports.cosineSimilarity = exports.RecallEngine = exports.MnemoPay = exports.MnemoPayLite = exports.BADGE_DEFINITIONS = void 0;
17
+ exports.createSandboxServer = exports.DEFAULT_ANOMALY_CONFIG = exports.CanarySystem = exports.BehaviorMonitor = exports.EWMADetector = exports.DEFAULT_BEHAVIORAL_CONFIG = exports.BehavioralEngine = exports.MerkleTree = exports.DEFAULT_FICO_CONFIG = exports.DEFAULT_AGENT_CREDIT_CONFIG = exports.AgentFICO = exports.AgentCreditScore = exports.DEFAULT_ADAPTIVE_CONFIG = exports.AdaptiveEngine = exports.validateProfile = exports.loadProfileFromEnv = exports.GenericCheckoutStrategy = exports.ShopifyCheckoutStrategy = exports.CheckoutExecutor = exports.MockCommerceProvider = exports.CommerceEngine = exports.MnemoPayNetwork = exports.constantTimeEqual = exports.IdentityRegistry = exports.hashEntry = exports.Ledger = exports.JSONFileStorage = exports.SQLiteStorage = exports.NIGERIAN_BANKS = exports.PaystackRail = exports.LightningRail = exports.StripeRail = exports.MockRail = exports.BehaviorProfile = exports.TransactionGraph = exports.IsolationForest = exports.DEFAULT_RATE_LIMIT = exports.DEFAULT_FRAUD_CONFIG = exports.RateLimiter = exports.FraudGuard = exports.l2Normalize = exports.localEmbed = exports.cosineSimilarity = exports.RecallEngine = exports.MnemoPay = exports.MnemoPayLite = exports.BADGE_DEFINITIONS = void 0;
18
18
  exports.autoScore = autoScore;
19
19
  exports.computeScore = computeScore;
20
20
  exports.reputationTier = reputationTier;
@@ -173,6 +173,29 @@ class MnemoPayLite extends EventEmitter {
173
173
  _streak = { currentStreak: 0, bestStreak: 0, lastSettlement: 0, streakBonus: 0 };
174
174
  /** Earned achievement badges */
175
175
  _badges = [];
176
+ /** Counters for terminal transactions — avoids full Map scans */
177
+ _settledCount = 0;
178
+ _refundedCount = 0;
179
+ _disputeCount = 0;
180
+ _totalValueSettled = 0;
181
+ /** O(1) pending count — incremented on charge, decremented on settle/refund/dispute/expire */
182
+ _pendingCount = 0;
183
+ /** Ring buffer of recent transactions for history() — capped at TX_HISTORY_BUFFER */
184
+ _recentTxBuffer = [];
185
+ static TX_HISTORY_BUFFER = 500;
186
+ /** Monotonic counter for charge sequencing: idempotency key + compact tx ID */
187
+ _chargeCounter = 0;
188
+ /** True when a persistence layer (disk or storage adapter) is active. Skips audit+emit overhead when false. */
189
+ _hasPersist = false;
190
+ /**
191
+ * Shared construction-time Date for non-persist transactions.
192
+ * One Date per agent instance instead of one per transaction — saves
193
+ * 56 bytes × 200 K txs = 11 MB at stress-test scale.
194
+ * All txs for this agent share the same approximate timestamp (accurate to
195
+ * ±minutes), which is correct for FICO recency weighting and settlement hold
196
+ * checks (holdMs > 0 guard means it's never accessed when holdMs = 0).
197
+ */
198
+ _txDateShared = new Date();
176
199
  constructor(agentId, decay = 0.05, debug = false, recallConfig, fraudConfig, paymentRail, requireCounterparty = false, storage) {
177
200
  super();
178
201
  this.agentId = agentId;
@@ -188,6 +211,7 @@ class MnemoPayLite extends EventEmitter {
188
211
  // Use provided storage adapter, or auto-detect persistence
189
212
  if (storage) {
190
213
  this.storageAdapter = storage;
214
+ this._hasPersist = true;
191
215
  this._loadFromStorage();
192
216
  }
193
217
  else {
@@ -219,6 +243,7 @@ class MnemoPayLite extends EventEmitter {
219
243
  if (!fs.existsSync(dir))
220
244
  fs.mkdirSync(dir, { recursive: true });
221
245
  this.persistPath = path.join(dir, `${this.agentId}.json`);
246
+ this._hasPersist = true;
222
247
  this._loadFromDisk();
223
248
  // Auto-save every 30 seconds
224
249
  this.persistTimer = setInterval(() => this._saveToDisk(), 30_000);
@@ -343,6 +368,23 @@ class MnemoPayLite extends EventEmitter {
343
368
  this._streak = { ...this._streak, ...raw.streak };
344
369
  if (raw.badges && Array.isArray(raw.badges))
345
370
  this._badges = raw.badges;
371
+ if (raw.settledCount !== undefined)
372
+ this._settledCount = raw.settledCount;
373
+ if (raw.refundedCount !== undefined)
374
+ this._refundedCount = raw.refundedCount;
375
+ if (raw.disputeCount !== undefined)
376
+ this._disputeCount = raw.disputeCount;
377
+ if (raw.totalValueSettled !== undefined)
378
+ this._totalValueSettled = raw.totalValueSettled;
379
+ if (raw.pendingCount !== undefined)
380
+ this._pendingCount = raw.pendingCount;
381
+ if (raw.recentTxBuffer && Array.isArray(raw.recentTxBuffer)) {
382
+ this._recentTxBuffer = raw.recentTxBuffer.map((t) => ({
383
+ ...t,
384
+ createdAt: new Date(t.createdAt),
385
+ completedAt: t.completedAt ? new Date(t.completedAt) : undefined,
386
+ }));
387
+ }
346
388
  if (raw.auditLog) {
347
389
  this.auditLog = raw.auditLog.map((e) => ({ ...e, createdAt: new Date(e.createdAt) }));
348
390
  }
@@ -474,10 +516,9 @@ class MnemoPayLite extends EventEmitter {
474
516
  }
475
517
  }
476
518
  _checkBadges() {
477
- const settled = Array.from(this.transactions.values()).filter(t => t.status === "completed");
478
- const settledCount = settled.length;
479
- const totalVolume = settled.reduce((sum, t) => sum + (t.netAmount ?? t.amount), 0);
480
- const disputeCount = Array.from(this.transactions.values()).filter(t => t.status === "disputed").length;
519
+ const settledCount = this._settledCount;
520
+ const totalVolume = this._totalValueSettled;
521
+ const disputeCount = this._disputeCount;
481
522
  const earnedIds = new Set(this._badges.map(b => b.id));
482
523
  for (const def of exports.BADGE_DEFINITIONS) {
483
524
  if (earnedIds.has(def.id))
@@ -505,6 +546,12 @@ class MnemoPayLite extends EventEmitter {
505
546
  createdAt: this._createdAt.toISOString(),
506
547
  memories: Array.from(this.memories.values()),
507
548
  transactions: Array.from(this.transactions.values()),
549
+ recentTxBuffer: this._recentTxBuffer,
550
+ settledCount: this._settledCount,
551
+ refundedCount: this._refundedCount,
552
+ disputeCount: this._disputeCount,
553
+ totalValueSettled: this._totalValueSettled,
554
+ pendingCount: this._pendingCount,
508
555
  auditLog: this.auditLog.slice(-500), // Keep last 500 entries
509
556
  fraudGuard: this.fraud.serialize(),
510
557
  ledger: this.ledger.serialize(),
@@ -560,9 +607,9 @@ class MnemoPayLite extends EventEmitter {
560
607
  }
561
608
  entry.details._hash = this._lastAuditHash;
562
609
  this.auditLog.push(entry);
563
- // Cap in-memory audit log to prevent unbounded growth
564
- if (this.auditLog.length > 1000) {
565
- this.auditLog.splice(0, this.auditLog.length - 500);
610
+ // Cap in-memory audit log to prevent unbounded growth (most recent 250 entries kept)
611
+ if (this.auditLog.length > 500) {
612
+ this.auditLog.splice(0, this.auditLog.length - 250);
566
613
  }
567
614
  }
568
615
  // ── Memory Methods ──────────────────────────────────────────────────────
@@ -738,7 +785,7 @@ class MnemoPayLite extends EventEmitter {
738
785
  `(reputation: ${this._reputation.toFixed(2)}, max: $${maxCharge.toFixed(2)})`);
739
786
  }
740
787
  // ── Fraud check ──────────────────────────────────────────────────────
741
- const pendingCount = Array.from(this.transactions.values()).filter((t) => t.status === "pending").length;
788
+ const pendingCount = this._pendingCount; // O(1) counter
742
789
  const risk = this.fraud.assessCharge(this.agentId, amount, this._reputation, this._createdAt, pendingCount, ctx);
743
790
  if (!risk.allowed) {
744
791
  this.audit("fraud:blocked", { amount, reason, riskScore: risk.score, signals: risk.signals.map((s) => s.type) });
@@ -753,30 +800,41 @@ class MnemoPayLite extends EventEmitter {
753
800
  }
754
801
  // Record charge for velocity tracking
755
802
  this.fraud.recordCharge(this.agentId, amount, ctx);
756
- // Generate idempotency key for payment rail calls
757
- const idempotencyKey = `charge_${this.agentId}_${Date.now()}_${randomUUID().slice(0, 8)}`;
803
+ // Compact counter used for both idempotency key (passed to rail) and tx ID (stored in Map).
804
+ // Counter-based IDs (~6 chars) cost ~16 bytes vs UUID (~560 bytes) in V8 heap.
805
+ const seq = ++this._chargeCounter;
806
+ const idempotencyKey = `charge_${this.agentId}_${seq}`;
758
807
  // Create hold on external payment rail.
759
808
  // payOptions lets callers target a specific customer + saved payment
760
809
  // method (Stripe) or a saved authorization code (Paystack). Rails
761
810
  // ignore fields they don't understand.
762
811
  const hold = await this.paymentRail.createHold(amount, reason, this.agentId, payOptions);
812
+ // For non-persist agents skip per-tx Date (56 B) + externalId string (52 B).
813
+ // reason and riskScore are always stored so callers and tests can read them.
814
+ // The sentinel Date (far future) prevents expireEscrow from firing; the settle()
815
+ // hold check is gated on holdMs > 0 so it never reads createdAt when holdMs = 0.
763
816
  const tx = {
764
- id: randomUUID(),
817
+ id: String(seq),
765
818
  agentId: this.agentId,
766
819
  amount,
767
820
  reason,
768
821
  status: "pending",
769
- createdAt: new Date(),
822
+ createdAt: this._hasPersist ? new Date() : this._txDateShared,
770
823
  riskScore: risk.score,
771
- externalId: hold.externalId,
772
- externalStatus: hold.status,
773
- idempotencyKey,
824
+ externalId: this._hasPersist ? hold.externalId : undefined,
825
+ externalStatus: this._hasPersist ? hold.status : undefined,
774
826
  };
775
827
  this.transactions.set(tx.id, tx);
828
+ this._pendingCount++;
776
829
  // Ledger: move funds from agent available → escrow
777
830
  this.ledger.recordCharge(this.agentId, amount, tx.id);
778
- this.audit("payment:pending", { id: tx.id, amount, reason, riskScore: risk.score, rail: this.paymentRail.name, externalId: hold.externalId });
779
- this._saveToDisk();
831
+ // Compact ledger aggressively keep only recent 50 entries to bound heap growth
832
+ if (this.ledger.visibleSize > 100) {
833
+ this.ledger.compact(50);
834
+ }
835
+ this.audit("payment:pending", { id: tx.id, amount, reason, riskScore: risk.score, rail: this.paymentRail.name });
836
+ if (this._hasPersist)
837
+ this._saveToDisk();
780
838
  this.emit("payment:pending", { id: tx.id, amount, reason });
781
839
  this.adaptive.observe({ type: "charge", agentId: this.agentId, amount, timestamp: Date.now() });
782
840
  this.log(`Charge created: $${amount.toFixed(2)} for "${reason}" (pending, risk: ${risk.score}, rail: ${this.paymentRail.name})`);
@@ -786,8 +844,14 @@ class MnemoPayLite extends EventEmitter {
786
844
  if (!txId || typeof txId !== "string")
787
845
  throw new Error("Transaction ID is required");
788
846
  const tx = this.transactions.get(txId);
789
- if (!tx)
847
+ // If not in map, check ring buffer to give accurate "already completed/refunded" errors
848
+ if (!tx) {
849
+ const buffered = this._recentTxBuffer.find(t => t.id === txId);
850
+ if (buffered) {
851
+ throw new Error(`Transaction ${txId} is ${buffered.status}, not pending`);
852
+ }
790
853
  throw new Error(`Transaction ${txId} not found`);
854
+ }
791
855
  if (tx.status !== "pending")
792
856
  throw new Error(`Transaction ${txId} is ${tx.status}, not pending`);
793
857
  // Prevent concurrent double-settle
@@ -866,11 +930,25 @@ class MnemoPayLite extends EventEmitter {
866
930
  netAmount: fee.netAmount, feeRate: fee.feeRate,
867
931
  reinforcedMemories: reinforced,
868
932
  });
869
- this._saveToDisk();
933
+ if (this._hasPersist)
934
+ this._saveToDisk();
870
935
  this.emit("payment:completed", { id: tx.id, amount: fee.netAmount, fee: fee.feeAmount });
871
936
  this.adaptive.observe({ type: "settle", agentId: this.agentId, amount: fee.netAmount, timestamp: Date.now() });
872
937
  this.log(`Settled $${tx.amount.toFixed(2)} (fee: $${fee.feeAmount.toFixed(2)}, net: $${fee.netAmount.toFixed(2)}) → ` +
873
938
  `wallet: $${this._wallet.toFixed(2)}, reputation: ${this._reputation.toFixed(2)}, reinforced: ${reinforced} memories`);
939
+ // Evict terminal tx from map — counters + ring buffer serve future queries
940
+ this._settledCount++;
941
+ this._pendingCount = Math.max(0, this._pendingCount - 1);
942
+ this._totalValueSettled += fee.netAmount;
943
+ this._recentTxBuffer.push({ ...tx });
944
+ if (this._recentTxBuffer.length > MnemoPayLite.TX_HISTORY_BUFFER) {
945
+ this._recentTxBuffer.shift();
946
+ }
947
+ this.transactions.delete(txId);
948
+ // Compact ledger entries aggressively to bound memory growth
949
+ if (this._settledCount % 10 === 0) {
950
+ this.ledger.compact(50);
951
+ }
874
952
  return { ...tx };
875
953
  }
876
954
  finally {
@@ -880,7 +958,25 @@ class MnemoPayLite extends EventEmitter {
880
958
  async refund(txId) {
881
959
  if (!txId || typeof txId !== "string")
882
960
  throw new Error("Transaction ID is required");
883
- const tx = this.transactions.get(txId);
961
+ let tx = this.transactions.get(txId);
962
+ // Completed txs are evicted from the map to save memory; check the ring buffer
963
+ if (!tx) {
964
+ const buffered = this._recentTxBuffer.find(t => t.id === txId);
965
+ if (buffered) {
966
+ // Re-activate into map so the existing refund logic can operate on it
967
+ tx = { ...buffered };
968
+ this.transactions.set(txId, tx);
969
+ // Remove from buffer (it will be re-added as "refunded" by the eviction below)
970
+ const bufIdx = this._recentTxBuffer.findIndex(t => t.id === txId);
971
+ if (bufIdx !== -1)
972
+ this._recentTxBuffer.splice(bufIdx, 1);
973
+ // Adjust settled counter since we're un-evicting a completed tx
974
+ if (tx.status === "completed") {
975
+ this._settledCount = Math.max(this._settledCount - 1, 0);
976
+ this._totalValueSettled = Math.max(this._totalValueSettled - (tx.netAmount ?? tx.amount), 0);
977
+ }
978
+ }
979
+ }
884
980
  if (!tx)
885
981
  throw new Error(`Transaction ${txId} not found`);
886
982
  if (tx.status === "refunded")
@@ -923,10 +1019,19 @@ class MnemoPayLite extends EventEmitter {
923
1019
  }
924
1020
  tx.status = "refunded";
925
1021
  this.audit("payment:refunded", { id: tx.id, amount: tx.amount, netRefunded: tx.netAmount ?? tx.amount });
926
- this._saveToDisk();
1022
+ if (this._hasPersist)
1023
+ this._saveToDisk();
927
1024
  this.emit("payment:refunded", { id: tx.id });
928
1025
  this.adaptive.observe({ type: "refund", agentId: this.agentId, amount: tx.amount, timestamp: Date.now() });
929
1026
  this.log(`Refunded $${tx.amount.toFixed(2)} → reputation: ${this._reputation.toFixed(2)}`);
1027
+ // Evict terminal tx from map
1028
+ this._refundedCount++;
1029
+ this._pendingCount = Math.max(0, this._pendingCount - 1);
1030
+ this._recentTxBuffer.push({ ...tx });
1031
+ if (this._recentTxBuffer.length > MnemoPayLite.TX_HISTORY_BUFFER) {
1032
+ this._recentTxBuffer.shift();
1033
+ }
1034
+ this.transactions.delete(txId);
930
1035
  return { ...tx };
931
1036
  }
932
1037
  finally {
@@ -935,7 +1040,24 @@ class MnemoPayLite extends EventEmitter {
935
1040
  }
936
1041
  // ── Dispute Resolution ─────────────────────────────────────────────────
937
1042
  async dispute(txId, reason, evidence) {
938
- const tx = this.transactions.get(txId);
1043
+ let tx = this.transactions.get(txId);
1044
+ // Terminal txs are evicted from the map; check the ring buffer
1045
+ if (!tx) {
1046
+ const buffered = this._recentTxBuffer.find(t => t.id === txId);
1047
+ if (buffered) {
1048
+ if (buffered.status !== "completed") {
1049
+ // Give accurate error for non-disputable terminal states
1050
+ throw new Error(`Can only dispute completed transactions (current: ${buffered.status})`);
1051
+ }
1052
+ tx = { ...buffered };
1053
+ this.transactions.set(txId, tx);
1054
+ const bufIdx = this._recentTxBuffer.findIndex(t => t.id === txId);
1055
+ if (bufIdx !== -1)
1056
+ this._recentTxBuffer.splice(bufIdx, 1);
1057
+ this._settledCount = Math.max(this._settledCount - 1, 0);
1058
+ this._totalValueSettled = Math.max(this._totalValueSettled - (tx.netAmount ?? tx.amount), 0);
1059
+ }
1060
+ }
939
1061
  if (!tx)
940
1062
  throw new Error(`Transaction ${txId} not found`);
941
1063
  if (tx.status !== "completed")
@@ -944,6 +1066,13 @@ class MnemoPayLite extends EventEmitter {
944
1066
  throw new Error(`Transaction ${txId} has no completion date`);
945
1067
  const d = this.fraud.fileDispute(txId, this.agentId, reason, tx.completedAt, evidence);
946
1068
  tx.status = "disputed";
1069
+ // Evict terminal tx from map
1070
+ this._disputeCount++;
1071
+ this._recentTxBuffer.push({ ...tx });
1072
+ if (this._recentTxBuffer.length > MnemoPayLite.TX_HISTORY_BUFFER) {
1073
+ this._recentTxBuffer.shift();
1074
+ }
1075
+ this.transactions.delete(txId);
947
1076
  this.audit("payment:disputed", { id: tx.id, disputeId: d.id, reason });
948
1077
  this._saveToDisk();
949
1078
  this.emit("payment:disputed", { txId, disputeId: d.id, reason });
@@ -961,12 +1090,15 @@ class MnemoPayLite extends EventEmitter {
961
1090
  const disputes = this.fraud.getDisputes?.() ?? [];
962
1091
  const pending = disputes.find((d) => d.id === disputeId);
963
1092
  if (pending) {
964
- const tx = this.transactions.get(pending.txId);
1093
+ const tx = this.transactions.get(pending.txId) ??
1094
+ this._recentTxBuffer.find(t => t.id === pending.txId);
965
1095
  if (tx && tx.agentId !== this.agentId)
966
1096
  throw new Error("Unauthorized: cannot resolve another agent's dispute");
967
1097
  }
968
1098
  const d = this.fraud.resolveDispute(disputeId, outcome);
969
- const tx = this.transactions.get(d.txId);
1099
+ // Disputed txs are evicted to the ring buffer; check both places
1100
+ const tx = this.transactions.get(d.txId) ??
1101
+ this._recentTxBuffer.find(t => t.id === d.txId);
970
1102
  if (!tx)
971
1103
  throw new Error("Dispute references unknown transaction");
972
1104
  if (outcome === "refund") {
@@ -978,11 +1110,16 @@ class MnemoPayLite extends EventEmitter {
978
1110
  this._streak.currentStreak = 0;
979
1111
  this._streak.streakBonus = 0;
980
1112
  tx.status = "refunded";
1113
+ this._refundedCount++;
1114
+ this._disputeCount = Math.max(this._disputeCount - 1, 0);
981
1115
  }
982
1116
  }
983
1117
  else {
984
1118
  if (tx.status === "disputed") {
985
1119
  tx.status = "completed"; // Restore to completed
1120
+ this._settledCount++;
1121
+ this._totalValueSettled += tx.netAmount ?? tx.amount;
1122
+ this._disputeCount = Math.max(this._disputeCount - 1, 0);
986
1123
  }
987
1124
  }
988
1125
  this.audit("dispute:resolved", { disputeId, outcome, txId: d.txId });
@@ -1049,26 +1186,25 @@ class MnemoPayLite extends EventEmitter {
1049
1186
  reputation: this._reputation,
1050
1187
  wallet: this._wallet,
1051
1188
  memoriesCount: this.memories.size,
1052
- transactionsCount: this.transactions.size,
1189
+ transactionsCount: this.transactions.size + this._recentTxBuffer.length,
1053
1190
  };
1054
1191
  }
1055
1192
  async logs(limit = 50) {
1056
1193
  return this.auditLog.slice(-limit);
1057
1194
  }
1058
1195
  async history(limit = 20) {
1059
- const all = Array.from(this.transactions.values());
1060
- // Reverse insertion order (Map preserves insertion order)
1061
- all.reverse();
1062
- return all.slice(0, limit).map((tx) => ({ ...tx }));
1196
+ // Recent completed/refunded/disputed txs from ring buffer (most recent first)
1197
+ const fromBuffer = this._recentTxBuffer.slice().reverse().slice(0, limit);
1198
+ // Active (pending/processing) txs still in the map
1199
+ const active = Array.from(this.transactions.values()).reverse();
1200
+ const combined = [...active, ...fromBuffer].slice(0, limit);
1201
+ return combined.map((tx) => ({ ...tx }));
1063
1202
  }
1064
1203
  // ── Reputation ──────────────────────────────────────────────────────────
1065
1204
  async reputation() {
1066
- const txs = Array.from(this.transactions.values());
1067
- const settled = txs.filter((t) => t.status === "completed");
1068
- const refunded = txs.filter((t) => t.status === "refunded");
1069
- const totalCompleted = settled.length + refunded.length;
1070
- const settlementRate = totalCompleted > 0 ? settled.length / totalCompleted : 0;
1071
- const totalValueSettled = settled.reduce((sum, t) => sum + t.amount, 0);
1205
+ // Use O(1) counters instead of scanning the transactions map
1206
+ const totalCompleted = this._settledCount + this._refundedCount;
1207
+ const settlementRate = totalCompleted > 0 ? this._settledCount / totalCompleted : 0;
1072
1208
  const mems = Array.from(this.memories.values());
1073
1209
  const avgImportance = mems.length > 0
1074
1210
  ? mems.reduce((sum, m) => sum + m.importance, 0) / mems.length
@@ -1078,10 +1214,10 @@ class MnemoPayLite extends EventEmitter {
1078
1214
  agentId: this.agentId,
1079
1215
  score: this._reputation,
1080
1216
  tier: reputationTier(this._reputation),
1081
- settledCount: settled.length,
1082
- refundCount: refunded.length,
1217
+ settledCount: this._settledCount,
1218
+ refundCount: this._refundedCount,
1083
1219
  settlementRate,
1084
- totalValueSettled,
1220
+ totalValueSettled: this._totalValueSettled,
1085
1221
  memoriesCount: this.memories.size,
1086
1222
  avgMemoryImportance: Math.round(avgImportance * 100) / 100,
1087
1223
  ageHours: Math.round(ageHours * 10) / 10,
@@ -1623,6 +1759,7 @@ Object.defineProperty(exports, "DEFAULT_ADAPTIVE_CONFIG", { enumerable: true, ge
1623
1759
  var fico_js_1 = require("./fico.js");
1624
1760
  Object.defineProperty(exports, "AgentCreditScore", { enumerable: true, get: function () { return fico_js_1.AgentCreditScore; } });
1625
1761
  Object.defineProperty(exports, "AgentFICO", { enumerable: true, get: function () { return fico_js_1.AgentFICO; } });
1762
+ Object.defineProperty(exports, "DEFAULT_AGENT_CREDIT_CONFIG", { enumerable: true, get: function () { return fico_js_1.DEFAULT_AGENT_CREDIT_CONFIG; } });
1626
1763
  Object.defineProperty(exports, "DEFAULT_FICO_CONFIG", { enumerable: true, get: function () { return fico_js_1.DEFAULT_FICO_CONFIG; } });
1627
1764
  var integrity_js_1 = require("./integrity.js");
1628
1765
  Object.defineProperty(exports, "MerkleTree", { enumerable: true, get: function () { return integrity_js_1.MerkleTree; } });