@gvnrdao/dh-sdk 0.0.293 → 0.0.294

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.mjs CHANGED
@@ -3750,7 +3750,7 @@ var require_abstract_coder = __commonJS({
3750
3750
  function toObject2(names2, items, deep) {
3751
3751
  if (names2.indexOf(null) >= 0) {
3752
3752
  return items.map((item, index) => {
3753
- if (item instanceof Result10) {
3753
+ if (item instanceof Result11) {
3754
3754
  return toObject2(getNames2(item), item, deep);
3755
3755
  }
3756
3756
  return item;
@@ -3759,7 +3759,7 @@ var require_abstract_coder = __commonJS({
3759
3759
  return names2.reduce((accum, name, index) => {
3760
3760
  let item = items.getValue(name);
3761
3761
  if (!(name in accum)) {
3762
- if (deep && item instanceof Result10) {
3762
+ if (deep && item instanceof Result11) {
3763
3763
  item = toObject2(getNames2(item), item, deep);
3764
3764
  }
3765
3765
  accum[name] = item;
@@ -3767,7 +3767,7 @@ var require_abstract_coder = __commonJS({
3767
3767
  return accum;
3768
3768
  }, {});
3769
3769
  }
3770
- var Result10 = class _Result extends Array {
3770
+ var Result11 = class _Result extends Array {
3771
3771
  // No longer used; but cannot be removed as it will remove the
3772
3772
  // #private field from the .d.ts which may break backwards
3773
3773
  // compatibility
@@ -3971,7 +3971,7 @@ var require_abstract_coder = __commonJS({
3971
3971
  return new _Result(_guard5, items, keys);
3972
3972
  }
3973
3973
  };
3974
- exports2.Result = Result10;
3974
+ exports2.Result = Result11;
3975
3975
  function checkResultErrors2(result) {
3976
3976
  const errors2 = [];
3977
3977
  const checkErrors = function(path2, object2) {
@@ -32409,8 +32409,8 @@ ${errorReport}`);
32409
32409
  var UTXO_QUERY_MAX_RETRIES = 3;
32410
32410
  var UTXO_QUERY_RETRY_DELAY_MS = 500;
32411
32411
  var QUANTUM_WINDOW_SECONDS2 = 60;
32412
- var DEAD_ZONE_SECONDS = 16;
32413
- var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS;
32412
+ var DEAD_ZONE_SECONDS2 = 16;
32413
+ var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS2;
32414
32414
  var SECONDS_PER_DAY = 86400;
32415
32415
  var DAYS_PER_MONTH = 30;
32416
32416
  var LIT_ACTION_ETH_RPC_TIMEOUT_MS = 8e3;
@@ -34949,8 +34949,8 @@ function getMainnetConfig() {
34949
34949
  bitcoinWithdrawalAddressRegistry: MAINNET_CONTRACTS.BitcoinWithdrawalAddressRegistry || ""
34950
34950
  },
34951
34951
  subgraphs: {
34952
- diamondHandsUrl: "https://api.studio.thegraph.com/query/1755201/diamond-hands/v1.0.0-mainnet"
34953
- // Mainnet subgraph (keyless studio endpoint, browser-queryable)
34952
+ diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
34953
+ // Published Sepolia subgraph on Arbitrum One
34954
34954
  },
34955
34955
  litNetwork: "chipotle",
34956
34956
  debug: false
@@ -108108,6 +108108,9 @@ function validateSDKConfig(config) {
108108
108108
 
108109
108109
  // src/utils/quantum-timing.ts
108110
108110
  var QUANTUM_WINDOW_SECONDS = 60;
108111
+ var DEAD_ZONE_SECONDS = 8;
108112
+ var INCLUSION_LATENCY_BUDGET = 16;
108113
+ var POST_BOUNDARY_SKEW_MARGIN = 3;
108111
108114
  function calculateNextQuantumTimestamp() {
108112
108115
  const now = Math.floor(Date.now() / 1e3);
108113
108116
  const currentQuantum = Math.floor(now / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -108136,6 +108139,27 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
108136
108139
  console.log(" Current quantum:", currentQuantum);
108137
108140
  console.log(" Valid window: PAST/CURRENT/NEXT (180s total)");
108138
108141
  }
108142
+ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
108143
+ const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
108144
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
108145
+ const nowSec = now();
108146
+ const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108147
+ const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108148
+ const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
108149
+ const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
108150
+ if (isNextQuantumSig && secsToBoundary <= DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET) {
108151
+ const target = currentQuantum + QUANTUM_WINDOW_SECONDS + POST_BOUNDARY_SKEW_MARGIN;
108152
+ const waitSeconds = target - nowSec;
108153
+ if (waitSeconds > 0) {
108154
+ console.log(
108155
+ `[Quantum Timing] \u23F3 Deferring send ${waitSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, current ${currentQuantum}, ${secsToBoundary}s to boundary)`
108156
+ );
108157
+ await sleep(waitSeconds * 1e3);
108158
+ return { waited: true, waitedSeconds: waitSeconds };
108159
+ }
108160
+ }
108161
+ return { waited: false, waitedSeconds: 0 };
108162
+ }
108139
108163
 
108140
108164
  // src/utils/mint-authorization.utils.ts
108141
108165
  var PKP_NFT_ABI = [
@@ -109745,106 +109769,365 @@ function createContractManager(config) {
109745
109769
  }
109746
109770
 
109747
109771
  // src/modules/cache/cache-manager.module.ts
109748
- var Cache = class {
109749
- cache = /* @__PURE__ */ new Map();
109772
+ var LRUCache = class {
109773
+ cache;
109774
+ /**
109775
+ * Audit M-J: singleflight registry for `getOrCompute` / `getOrComputeResult`.
109776
+ * Concurrent cache-miss callers for the same key share one inflight promise
109777
+ * instead of each running `compute()` independently — important when the
109778
+ * compute spends a paid LIT capacity credit or hits a rate-limited upstream.
109779
+ */
109780
+ inflight = /* @__PURE__ */ new Map();
109781
+ inflightResult = /* @__PURE__ */ new Map();
109750
109782
  maxSize;
109751
109783
  ttlMs;
109752
- constructor(config) {
109753
- this.maxSize = config.maxSize;
109754
- this.ttlMs = config.ttlMs;
109784
+ debug;
109785
+ name;
109786
+ // Statistics
109787
+ stats = {
109788
+ hits: 0,
109789
+ misses: 0,
109790
+ evictions: 0
109791
+ };
109792
+ constructor(config = {}) {
109793
+ this.cache = /* @__PURE__ */ new Map();
109794
+ this.maxSize = config.maxSize || 1e3;
109795
+ this.ttlMs = config.ttlMs || 6e4;
109796
+ this.debug = config.debug || false;
109797
+ this.name = config.name || "Cache";
109798
+ if (this.debug) {
109799
+ console.log(
109800
+ `\u{1F4BE} [${this.name}] Initialized: maxSize=${this.maxSize}, ttl=${this.ttlMs}ms`
109801
+ );
109802
+ }
109755
109803
  }
109756
109804
  /**
109757
109805
  * Get value from cache
109806
+ *
109807
+ * Returns null if:
109808
+ * - Key not found
109809
+ * - Entry has expired
109810
+ *
109811
+ * @param key - Cache key
109812
+ * @returns Cached value or null
109758
109813
  */
109759
109814
  get(key) {
109760
109815
  const entry = this.cache.get(key);
109761
109816
  if (!entry) {
109762
- return void 0;
109817
+ this.stats.misses++;
109818
+ if (this.debug) {
109819
+ console.log(`\u274C [${this.name}] Cache MISS: ${String(key)}`);
109820
+ }
109821
+ return null;
109763
109822
  }
109764
- const now = Date.now();
109765
- if (now - entry.timestamp > entry.ttl) {
109823
+ if (this.isExpired(entry)) {
109766
109824
  this.cache.delete(key);
109767
- return void 0;
109825
+ this.stats.misses++;
109826
+ if (this.debug) {
109827
+ const age = Date.now() - entry.timestamp;
109828
+ console.log(`\u23F0 [${this.name}] Cache EXPIRED: ${String(key)} (age: ${age}ms)`);
109829
+ }
109830
+ return null;
109831
+ }
109832
+ entry.hits++;
109833
+ entry.lastAccessed = Date.now();
109834
+ this.cache.set(key, entry);
109835
+ this.stats.hits++;
109836
+ if (this.debug) {
109837
+ const age = Date.now() - entry.timestamp;
109838
+ console.log(
109839
+ `\u2705 [${this.name}] Cache HIT: ${String(key)} (age: ${age}ms, hits: ${entry.hits})`
109840
+ );
109768
109841
  }
109769
109842
  return entry.value;
109770
109843
  }
109844
+ /**
109845
+ * Get value from cache with Result wrapper
109846
+ *
109847
+ * Useful when you want to distinguish between "not found" and "expired"
109848
+ */
109849
+ getResult(key) {
109850
+ const value = this.get(key);
109851
+ if (value === null) {
109852
+ return failure(
109853
+ new SDKError({
109854
+ message: `Cache miss for key: ${String(key)}`,
109855
+ category: "CACHE" /* CACHE */,
109856
+ severity: "LOW" /* LOW */,
109857
+ originalError: new Error("Cache miss")
109858
+ })
109859
+ );
109860
+ }
109861
+ return success(value);
109862
+ }
109771
109863
  /**
109772
109864
  * Set value in cache
109865
+ *
109866
+ * If cache is full, evicts the least recently used entry
109867
+ *
109868
+ * @param key - Cache key
109869
+ * @param value - Value to cache
109870
+ * @param ttl - Optional custom TTL for this entry (ms)
109773
109871
  */
109774
109872
  set(key, value, ttl) {
109775
109873
  if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
109776
- const firstKey = this.cache.keys().next().value;
109777
- if (firstKey) {
109778
- this.cache.delete(firstKey);
109779
- }
109874
+ this.evictLRU();
109780
109875
  }
109781
- this.cache.set(key, {
109876
+ const entry = {
109782
109877
  value,
109783
109878
  timestamp: Date.now(),
109784
- ttl: ttl || this.ttlMs
109785
- });
109879
+ hits: 0,
109880
+ lastAccessed: Date.now()
109881
+ };
109882
+ this.cache.set(key, entry);
109883
+ if (this.debug) {
109884
+ const effectiveTtl = ttl || this.ttlMs;
109885
+ console.log(
109886
+ `\u{1F4BE} [${this.name}] Cache SET: ${String(key)} (ttl: ${effectiveTtl}ms, size: ${this.cache.size}/${this.maxSize})`
109887
+ );
109888
+ }
109786
109889
  }
109787
109890
  /**
109788
- * Check if key exists in cache
109891
+ * Set value in cache with Result wrapper
109892
+ */
109893
+ setResult(key, value, ttl) {
109894
+ try {
109895
+ this.set(key, value, ttl);
109896
+ return success(void 0);
109897
+ } catch (error2) {
109898
+ return failure(
109899
+ new SDKError({
109900
+ message: `Failed to set cache value for key: ${String(key)}`,
109901
+ category: "CACHE" /* CACHE */,
109902
+ severity: "MEDIUM" /* MEDIUM */,
109903
+ originalError: error2 instanceof Error ? error2 : new Error(String(error2))
109904
+ })
109905
+ );
109906
+ }
109907
+ }
109908
+ /**
109909
+ * Check if key exists in cache (without affecting stats)
109789
109910
  */
109790
109911
  has(key) {
109791
- return this.get(key) !== void 0;
109912
+ const entry = this.cache.get(key);
109913
+ return entry !== void 0 && !this.isExpired(entry);
109792
109914
  }
109793
109915
  /**
109794
- * Delete key from cache
109916
+ * Delete specific key from cache
109795
109917
  */
109796
109918
  delete(key) {
109797
- return this.cache.delete(key);
109919
+ const deleted = this.cache.delete(key);
109920
+ if (deleted && this.debug) {
109921
+ console.log(`\u{1F5D1}\uFE0F [${this.name}] Cache DELETE: ${String(key)}`);
109922
+ }
109923
+ return deleted;
109798
109924
  }
109799
109925
  /**
109800
- * Clear all cache entries
109926
+ * Clear entire cache
109801
109927
  */
109802
109928
  clear() {
109929
+ const previousSize = this.cache.size;
109803
109930
  this.cache.clear();
109931
+ this.stats = {
109932
+ hits: 0,
109933
+ misses: 0,
109934
+ evictions: 0
109935
+ };
109936
+ if (this.debug) {
109937
+ console.log(`\u{1F9F9} [${this.name}] Cache CLEARED: removed ${previousSize} entries`);
109938
+ }
109804
109939
  }
109805
109940
  /**
109806
- * Clean expired entries
109941
+ * Get current cache size
109942
+ */
109943
+ size() {
109944
+ return this.cache.size;
109945
+ }
109946
+ /**
109947
+ * Get cache statistics
109948
+ */
109949
+ getStats() {
109950
+ const entries = Array.from(this.cache.values());
109951
+ const timestamps = entries.map((e) => e.timestamp);
109952
+ const total = this.stats.hits + this.stats.misses;
109953
+ const hitRate = total === 0 ? 0 : this.stats.hits / total * 100;
109954
+ return {
109955
+ size: this.cache.size,
109956
+ hits: this.stats.hits,
109957
+ misses: this.stats.misses,
109958
+ evictions: this.stats.evictions,
109959
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : 0,
109960
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : 0,
109961
+ hitRate
109962
+ };
109963
+ }
109964
+ /**
109965
+ * Get hit rate percentage
109966
+ */
109967
+ getHitRate() {
109968
+ const total = this.stats.hits + this.stats.misses;
109969
+ return total === 0 ? 0 : this.stats.hits / total * 100;
109970
+ }
109971
+ /**
109972
+ * Get all cached keys (for debugging)
109973
+ */
109974
+ getKeys() {
109975
+ return Array.from(this.cache.keys());
109976
+ }
109977
+ /**
109978
+ * Get all cached values (for debugging)
109979
+ */
109980
+ getValues() {
109981
+ return Array.from(this.cache.values()).map((entry) => entry.value);
109982
+ }
109983
+ /**
109984
+ * Get all cache entries with metadata (for debugging)
109985
+ */
109986
+ getEntries() {
109987
+ return Array.from(this.cache.entries()).map(([key, entry]) => ({
109988
+ key,
109989
+ value: entry.value,
109990
+ metadata: {
109991
+ timestamp: entry.timestamp,
109992
+ hits: entry.hits,
109993
+ lastAccessed: entry.lastAccessed
109994
+ }
109995
+ }));
109996
+ }
109997
+ /**
109998
+ * Clean up expired entries
109999
+ *
110000
+ * Useful for periodic maintenance
110001
+ *
110002
+ * @returns Number of entries cleaned
109807
110003
  */
109808
110004
  cleanExpired() {
109809
110005
  const now = Date.now();
109810
- let cleaned = 0;
110006
+ let cleanedCount = 0;
109811
110007
  for (const [key, entry] of this.cache.entries()) {
109812
- if (now - entry.timestamp > entry.ttl) {
110008
+ if (now - entry.timestamp > this.ttlMs) {
109813
110009
  this.cache.delete(key);
109814
- cleaned++;
110010
+ cleanedCount++;
109815
110011
  }
109816
110012
  }
109817
- return cleaned;
110013
+ if (cleanedCount > 0 && this.debug) {
110014
+ console.log(`\u{1F9F9} [${this.name}] Cleaned ${cleanedCount} expired entries`);
110015
+ }
110016
+ return cleanedCount;
109818
110017
  }
109819
110018
  /**
109820
- * Get cache statistics
110019
+ * Check if cache entry is expired
109821
110020
  */
109822
- getStats() {
109823
- return {
109824
- size: this.cache.size,
109825
- maxSize: this.maxSize,
109826
- ttlMs: this.ttlMs
109827
- };
110021
+ isExpired(entry) {
110022
+ return Date.now() - entry.timestamp > this.ttlMs;
110023
+ }
110024
+ /**
110025
+ * Evict least recently used entry
110026
+ */
110027
+ evictLRU() {
110028
+ let oldestKey = null;
110029
+ let oldestAccess = Infinity;
110030
+ for (const [key, entry] of this.cache.entries()) {
110031
+ if (entry.lastAccessed < oldestAccess) {
110032
+ oldestAccess = entry.lastAccessed;
110033
+ oldestKey = key;
110034
+ }
110035
+ }
110036
+ if (oldestKey !== null) {
110037
+ this.cache.delete(oldestKey);
110038
+ this.stats.evictions++;
110039
+ if (this.debug) {
110040
+ const timeSinceAccess = Date.now() - oldestAccess;
110041
+ console.log(
110042
+ `\u267B\uFE0F [${this.name}] Cache EVICT (LRU): ${String(oldestKey)} (last accessed: ${timeSinceAccess}ms ago)`
110043
+ );
110044
+ }
110045
+ }
110046
+ }
110047
+ /**
110048
+ * Get or compute value
110049
+ *
110050
+ * If key exists in cache, returns cached value.
110051
+ * Otherwise, computes value using provided function and caches it.
110052
+ *
110053
+ * @param key - Cache key
110054
+ * @param compute - Function to compute value if not in cache
110055
+ * @param ttl - Optional custom TTL for this entry
110056
+ * @returns Cached or computed value
110057
+ */
110058
+ async getOrCompute(key, compute, ttl) {
110059
+ const cached = this.get(key);
110060
+ if (cached !== null) {
110061
+ return cached;
110062
+ }
110063
+ const existing = this.inflight.get(key);
110064
+ if (existing) {
110065
+ return existing;
110066
+ }
110067
+ const computePromise = (async () => {
110068
+ try {
110069
+ const value = await compute();
110070
+ this.set(key, value, ttl);
110071
+ return value;
110072
+ } finally {
110073
+ this.inflight.delete(key);
110074
+ }
110075
+ })();
110076
+ this.inflight.set(key, computePromise);
110077
+ return computePromise;
110078
+ }
110079
+ /**
110080
+ * Get or compute value with Result wrapper
110081
+ */
110082
+ async getOrComputeResult(key, compute, ttl) {
110083
+ const cached = this.get(key);
110084
+ if (cached !== null) {
110085
+ return success(cached);
110086
+ }
110087
+ const existing = this.inflightResult.get(key);
110088
+ if (existing) {
110089
+ return existing;
110090
+ }
110091
+ const computePromise = (async () => {
110092
+ try {
110093
+ return await compute();
110094
+ } finally {
110095
+ this.inflightResult.delete(key);
110096
+ }
110097
+ })();
110098
+ this.inflightResult.set(key, computePromise);
110099
+ const result = await computePromise;
110100
+ if (result.success) {
110101
+ this.set(key, result.value, ttl);
110102
+ }
110103
+ return result;
109828
110104
  }
109829
110105
  };
109830
110106
  var CacheManager = class {
109831
110107
  caches = /* @__PURE__ */ new Map();
109832
- debug;
109833
- constructor(config = {}) {
109834
- this.debug = config.debug || false;
110108
+ globalConfig;
110109
+ constructor(globalConfig = {}) {
110110
+ this.globalConfig = globalConfig;
109835
110111
  }
109836
110112
  /**
109837
- * Get or create a cache instance
110113
+ * Create or get a named cache
110114
+ *
110115
+ * @param name - Unique cache name
110116
+ * @param config - Optional cache-specific configuration
110117
+ * @returns LRU cache instance
109838
110118
  */
109839
110119
  getCache(name, config) {
109840
- if (this.caches.has(name)) {
109841
- return this.caches.get(name);
110120
+ const existingCache = this.caches.get(name);
110121
+ if (existingCache) {
110122
+ return existingCache;
109842
110123
  }
109843
- const cache = new Cache(config);
110124
+ const mergedConfig = {
110125
+ ...this.globalConfig,
110126
+ ...config,
110127
+ name
110128
+ };
110129
+ const cache = new LRUCache(mergedConfig);
109844
110130
  this.caches.set(name, cache);
109845
- if (this.debug) {
109846
- console.log(`[CacheManager] Created cache: ${name}`, config);
109847
- }
109848
110131
  return cache;
109849
110132
  }
109850
110133
  /**
@@ -109859,11 +110142,11 @@ var CacheManager = class {
109859
110142
  * Clean expired entries from all caches
109860
110143
  */
109861
110144
  cleanAllExpired() {
109862
- let total = 0;
110145
+ let totalCleaned = 0;
109863
110146
  for (const cache of this.caches.values()) {
109864
- total += cache.cleanExpired();
110147
+ totalCleaned += cache.cleanExpired();
109865
110148
  }
109866
- return total;
110149
+ return totalCleaned;
109867
110150
  }
109868
110151
  /**
109869
110152
  * Get statistics for all caches
@@ -109876,11 +110159,16 @@ var CacheManager = class {
109876
110159
  return stats;
109877
110160
  }
109878
110161
  /**
109879
- * Destroy cache manager
110162
+ * Get list of all cache names
109880
110163
  */
109881
- destroy() {
109882
- this.clearAll();
109883
- this.caches.clear();
110164
+ getCacheNames() {
110165
+ return Array.from(this.caches.keys());
110166
+ }
110167
+ /**
110168
+ * Delete a named cache
110169
+ */
110170
+ deleteCache(name) {
110171
+ return this.caches.delete(name);
109884
110172
  }
109885
110173
  };
109886
110174
  function createCacheManager(config) {
@@ -118373,12 +118661,11 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118373
118661
  {}
118374
118662
  );
118375
118663
  }
118376
- try {
118377
- return await this._requestMintUCDAttempt(request);
118378
- } catch (error2) {
118379
- const errorMsg = error2.message || String(error2);
118664
+ const result = await this._requestMintUCDAttempt(request);
118665
+ if (!result.success && fullRetry < MAX_FULL_RETRIES) {
118666
+ const errorMsg = result.error || "";
118380
118667
  const isStaleTimestamp = errorMsg.includes("quantumTimestamp too old") || errorMsg.includes("Timestamp staleness") || errorMsg.includes("MAX_SKEW") || errorMsg.includes("Quantum window expired") || errorMsg.includes("Too close to quantum boundary") || errorMsg.includes("QuantumOutsideWindow") || errorMsg.includes("Quantum window remaining too low");
118381
- if (isStaleTimestamp && fullRetry < MAX_FULL_RETRIES) {
118668
+ if (isStaleTimestamp) {
118382
118669
  if (this.config.debug) {
118383
118670
  log.warn(
118384
118671
  `\u26A0\uFE0F Timestamp became stale during mint process. Retrying from beginning...`,
@@ -118388,8 +118675,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118388
118675
  await new Promise((resolve) => setTimeout(resolve, 2e3));
118389
118676
  continue;
118390
118677
  }
118391
- throw error2;
118392
118678
  }
118679
+ return result;
118393
118680
  }
118394
118681
  return {
118395
118682
  success: false,
@@ -119011,6 +119298,7 @@ Context: Quantum timestamp=${validationResponse.timestamp}, Position=${request.p
119011
119298
  "0x131d9a21": "QuantumExpired()",
119012
119299
  "0x52ce5d58": "QuantumAlreadyUsed()",
119013
119300
  "0x137f3b70": "InDeadZone()",
119301
+ "0xbe4b82c1": "DeadZoneViolation()",
119014
119302
  "0x3e76a3c9": "QuantumOutsideWindow()",
119015
119303
  "0x62278171": "InvalidValidatorSignature()",
119016
119304
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119031,7 +119319,7 @@ Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMi
119031
119319
  Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
119032
119320
  ` : "";
119033
119321
  let quantumContext = "";
119034
- if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0x3e76a3c9") {
119322
+ if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0xbe4b82c1" || selector === "0x3e76a3c9") {
119035
119323
  const currentTime2 = Math.floor(Date.now() / 1e3);
119036
119324
  const currentQuantum = Math.floor(currentTime2 / 60) * 60;
119037
119325
  const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
@@ -119293,6 +119581,7 @@ Position: ${request.positionId}`
119293
119581
  validationResponse.timestamp,
119294
119582
  signatureHexMint
119295
119583
  ]);
119584
+ await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
119296
119585
  const fromAddress = await signer.getAddress();
119297
119586
  const estimatedGas = await estimateContractCallGasWithMargin(
119298
119587
  signerProvider,
@@ -119351,6 +119640,7 @@ Position: ${request.positionId}`
119351
119640
  "0x131d9a21": "QuantumExpired()",
119352
119641
  "0x52ce5d58": "QuantumAlreadyUsed()",
119353
119642
  "0x137f3b70": "InDeadZone()",
119643
+ "0xbe4b82c1": "DeadZoneViolation()",
119354
119644
  "0x3e76a3c9": "QuantumOutsideWindow()",
119355
119645
  "0x62278171": "InvalidValidatorSignature()",
119356
119646
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119530,6 +119820,7 @@ Error data: ${errorData || "none"}`
119530
119820
  "0x131d9a21": "QuantumExpired() - Signature quantum window has closed",
119531
119821
  "0x52ce5d58": "QuantumAlreadyUsed() - This quantum was already used for this position",
119532
119822
  "0x137f3b70": "InDeadZone() - Timestamp in dead zone (near quantum boundary)",
119823
+ "0xbe4b82c1": "DeadZoneViolation() - Non-current-quantum signature mined in the last 8s of the current quantum",
119533
119824
  "0x3e76a3c9": "QuantumOutsideWindow() - Timestamp not in past/current/next quantum window"
119534
119825
  };
119535
119826
  const errorName = knownErrors[selector] || `Unknown error ${selector}`;
@@ -120598,6 +120889,7 @@ Error data: ${errorData || "none"}`
120598
120889
  );
120599
120890
  }
120600
120891
  const positionManager = positionManagerResult.value;
120892
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
120601
120893
  const tx = await positionManager["extendPosition"](
120602
120894
  positionIdBytes32,
120603
120895
  BigInt(_selectedTerm),
@@ -121030,6 +121322,57 @@ Error data: ${errorData || "none"}`
121030
121322
  if (!pauseCheck.ok) {
121031
121323
  return { success: false, error: pauseCheck.error };
121032
121324
  }
121325
+ const MAX_FULL_RETRIES = 3;
121326
+ for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
121327
+ if (fullRetry > 1 && this.config.debug) {
121328
+ log.info(
121329
+ `\u{1F504} Full payment retry ${fullRetry}/${MAX_FULL_RETRIES} due to quantum-timing failure...`,
121330
+ {}
121331
+ );
121332
+ }
121333
+ const result = await this._makePaymentAttempt(request);
121334
+ if (fullRetry < MAX_FULL_RETRIES && this.isRetryablePaymentQuantumFailure(result)) {
121335
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
121336
+ continue;
121337
+ }
121338
+ return result;
121339
+ }
121340
+ return {
121341
+ success: false,
121342
+ error: "Max retries exceeded for payment operation"
121343
+ };
121344
+ } finally {
121345
+ this.invalidateCachesForPosition(request.positionId);
121346
+ this.releaseWriteLock(request.positionId);
121347
+ }
121348
+ }
121349
+ /**
121350
+ * Whether a failed payment attempt is a quantum-timing failure that a fresh
121351
+ * re-sign can fix (safe to retry), versus a terminal failure.
121352
+ *
121353
+ * Retryable: pre-send simulation quantum errors, or an atomic mined revert
121354
+ * (status 0) — no funds moved and the quantum was not recorded on-chain, so a
121355
+ * fresh signature + resubmit is safe.
121356
+ *
121357
+ * NOT retryable: a confirmation timeout — the original tx may still be pending, so
121358
+ * a resubmit could double-pay. It is surfaced (with its tx hash) instead.
121359
+ */
121360
+ isRetryablePaymentQuantumFailure(result) {
121361
+ if (result.success || !result.error)
121362
+ return false;
121363
+ const e = result.error;
121364
+ if (e.includes("Transaction timeout"))
121365
+ return false;
121366
+ return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
121367
+ }
121368
+ /**
121369
+ * One payment attempt: user auth → Lit Action authorization → dead-zone gate →
121370
+ * pre-send simulation → broadcast → confirmation. Always resolves to a
121371
+ * PartialPaymentResult (never throws to the caller); the makePayment wrapper owns
121372
+ * the write lock, the pause pre-check, and the bounded re-sign retry loop.
121373
+ */
121374
+ async _makePaymentAttempt(request) {
121375
+ try {
121033
121376
  if (this.config.debug) {
121034
121377
  log.info(`\u{1F4B3} Making payment...`, {});
121035
121378
  log.info(` Request object:`, { request });
@@ -121290,7 +121633,6 @@ Error data: ${errorData || "none"}`
121290
121633
  error: `Failed to get PositionManager: ${positionManagerResult.error.message}`
121291
121634
  };
121292
121635
  }
121293
- const positionManager = positionManagerResult.value;
121294
121636
  if (this.config.debug) {
121295
121637
  log.info(
121296
121638
  ` About to call toBytes32 with: ${request.positionId} (type: ${typeof request.positionId})`
@@ -121350,9 +121692,6 @@ Error data: ${errorData || "none"}`
121350
121692
  currentQuantum
121351
121693
  });
121352
121694
  }
121353
- if (this.config.debug) {
121354
- log.info("\u23ED\uFE0F Skipping dead zone check to isolate BigNumber issue", {});
121355
- }
121356
121695
  if (this.config.debug) {
121357
121696
  log.info("\u{1F50D} Final timestamp validation:", {
121358
121697
  litActionTimestamp: litActionResult.timestamp,
@@ -121419,108 +121758,100 @@ Error data: ${errorData || "none"}`
121419
121758
  quantumTimestamp: quantumTimestamp.toString()
121420
121759
  });
121421
121760
  }
121422
- let tx;
121423
- try {
121424
- if (this.config.debug) {
121425
- log.info("\u{1F50D} Attempting contract interface call to makePayment");
121426
- }
121427
- if (this.config.debug) {
121428
- log.info("\u{1F50D} Contract call parameters:", {
121429
- positionIdBytes32,
121430
- paymentAmountWei: paymentAmountWei.toString(),
121431
- quantumTimestamp: quantumTimestamp.toString(),
121432
- btcPrice: btcPrice.toString(),
121433
- signatureLength: signature.length
121434
- });
121435
- }
121436
- if (typeof quantumTimestamp !== "bigint") {
121437
- throw new Error(
121438
- `quantumTimestamp is not a BigInt: ${typeof quantumTimestamp}, value: ${quantumTimestamp}`
121439
- );
121440
- }
121441
- if (typeof btcPrice !== "bigint") {
121442
- throw new Error(
121443
- `btcPrice is not a BigInt: ${typeof btcPrice}, value: ${btcPrice}`
121444
- );
121445
- }
121446
- if (typeof paymentAmountWei !== "bigint") {
121447
- throw new Error(
121448
- `paymentAmountWei is not a BigNumber: ${typeof paymentAmountWei}, value: ${paymentAmountWei}`
121449
- );
121450
- }
121451
- const paymentAmountStr = paymentAmountWei.toString();
121452
- const quantumTimestampStr = quantumTimestamp.toString();
121453
- const btcPriceStr = btcPrice.toString();
121454
- if (this.config.debug) {
121455
- log.info("\u{1F50D} About to call contract makePayment with:", {
121456
- positionIdBytes32: positionIdBytes32.substring(0, 20) + "...",
121457
- paymentAmountWei: paymentAmountStr,
121458
- quantumTimestamp: quantumTimestampStr,
121459
- btcPrice: btcPriceStr,
121460
- signature: signature.substring(0, 20) + "..."
121461
- });
121462
- }
121463
- if (typeof paymentAmountStr !== "string" || paymentAmountStr === "[object Object]") {
121464
- throw new Error(`Invalid paymentAmount: ${paymentAmountStr}`);
121465
- }
121466
- if (typeof quantumTimestampStr !== "string" || quantumTimestampStr === "[object Object]") {
121467
- throw new Error(`Invalid quantumTimestamp: ${quantumTimestampStr}`);
121468
- }
121469
- if (typeof btcPriceStr !== "string" || btcPriceStr === "[object Object]") {
121470
- throw new Error(`Invalid btcPrice: ${btcPriceStr}`);
121471
- }
121472
- if (this.config.debug) {
121473
- log.info("\u{1F50D} Final parameter validation before contract call:", {
121474
- positionIdBytes32: typeof positionIdBytes32,
121475
- paymentAmountStr: typeof paymentAmountStr + " = " + paymentAmountStr.substring(0, 20),
121476
- quantumTimestampStr: typeof quantumTimestampStr + " = " + quantumTimestampStr.substring(0, 20),
121477
- btcPriceStr: typeof btcPriceStr + " = " + btcPriceStr.substring(0, 20),
121478
- signature: typeof signature + " = " + signature.substring(0, 20)
121761
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121762
+ const paymentAmountStr = paymentAmountWei.toString();
121763
+ const quantumTimestampStr = quantumTimestamp.toString();
121764
+ const btcPriceStr = btcPrice.toString();
121765
+ const paymentContractAddress = this.getContractAddressesOrThrow().positionManager;
121766
+ const paymentSigner = this.getSignerOrThrow();
121767
+ const paymentFrom = await paymentSigner.getAddress();
121768
+ const paymentProvider = contractManager.getProvider();
121769
+ const paymentIface = new Interface([
121770
+ "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121771
+ ]);
121772
+ const paymentCalldata = paymentIface.encodeFunctionData("makePayment", [
121773
+ positionIdBytes32,
121774
+ paymentAmountStr,
121775
+ quantumTimestampStr,
121776
+ btcPriceStr,
121777
+ signature
121778
+ ]);
121779
+ const decodePaymentRevert = (e) => {
121780
+ const raw = e?.data ?? e?.info?.error?.data ?? e?.error?.data?.data ?? e?.error?.data;
121781
+ const data = typeof raw === "string" ? raw : void 0;
121782
+ if (!data || !data.startsWith("0x") || data.length < 10)
121783
+ return null;
121784
+ const selector = data.slice(0, 10);
121785
+ const map3 = {
121786
+ "0xbe4b82c1": "DeadZoneViolation()",
121787
+ "0x137f3b70": "InDeadZone()",
121788
+ "0x52ce5d58": "QuantumAlreadyUsed()",
121789
+ "0x3e76a3c9": "QuantumOutsideWindow()",
121790
+ "0x131d9a21": "QuantumExpired()",
121791
+ "0x8baa579f": "InvalidSignature()",
121792
+ "0x62278171": "InvalidValidatorSignature()",
121793
+ "0x3ee5aeb5": "OperationNotAuthorized()",
121794
+ "0x48f5c3ed": "Unauthorized()"
121795
+ };
121796
+ return map3[selector] ?? `Unknown error ${selector}`;
121797
+ };
121798
+ const MAX_DEADZONE_RESIMULATIONS = 3;
121799
+ for (let sim = 1; ; sim++) {
121800
+ try {
121801
+ await paymentProvider.call({
121802
+ to: paymentContractAddress,
121803
+ from: paymentFrom,
121804
+ data: paymentCalldata
121479
121805
  });
121480
- }
121481
- tx = await positionManager["makePayment"](
121482
- positionIdBytes32,
121483
- paymentAmountStr,
121484
- quantumTimestampStr,
121485
- btcPriceStr,
121486
- signature
121487
- );
121488
- if (this.config.debug) {
121489
- log.info("\u2705 Contract call succeeded", { txHash: tx.hash });
121490
- }
121491
- } catch (contractError) {
121492
- if (this.config.debug) {
121493
- log.warn(
121494
- "\u26A0\uFE0F Contract interface failed, falling back to raw transaction",
121495
- {
121496
- error: contractError instanceof Error ? contractError.message : String(contractError)
121806
+ break;
121807
+ } catch (simError) {
121808
+ const decoded = decodePaymentRevert(simError);
121809
+ if (decoded === "DeadZoneViolation()" && sim <= MAX_DEADZONE_RESIMULATIONS) {
121810
+ const nowSec = Math.floor(Date.now() / 1e3);
121811
+ const sigQuantum = Math.floor(Number(quantumTimestamp) / 60) * 60;
121812
+ const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
121813
+ if (sigQuantum === realCurrentQuantum) {
121814
+ if (this.config.debug) {
121815
+ log.info(
121816
+ "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
121817
+ {}
121818
+ );
121819
+ }
121820
+ break;
121497
121821
  }
121498
- );
121499
- }
121500
- const contractAddress = this.getContractAddressesOrThrow().positionManager;
121501
- const iface = new Interface([
121502
- "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121503
- ]);
121504
- const calldata = iface.encodeFunctionData("makePayment", [
121505
- positionIdBytes32,
121506
- paymentAmountWei.toString(),
121507
- quantumTimestamp.toString(),
121508
- btcPrice.toString(),
121509
- signature
121510
- ]);
121511
- const signer2 = this.getSignerOrThrow();
121512
- tx = await signer2.sendTransaction({
121513
- to: contractAddress,
121514
- data: calldata,
121515
- value: "0x0",
121516
- gasLimit: 5e5
121517
- });
121518
- if (this.config.debug) {
121519
- log.info("\u2705 Raw transaction fallback succeeded", {
121520
- txHash: tx.hash
121521
- });
121822
+ const gate = await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121823
+ if (gate.waited) {
121824
+ if (this.config.debug) {
121825
+ log.info(
121826
+ `\u{1F501} Re-simulating makePayment after crossing the quantum boundary (attempt ${sim}/${MAX_DEADZONE_RESIMULATIONS})`,
121827
+ {}
121828
+ );
121829
+ }
121830
+ continue;
121831
+ }
121832
+ }
121833
+ const label = decoded ?? simError?.reason ?? simError?.shortMessage ?? (simError instanceof Error ? simError.message : String(simError));
121834
+ if (this.config.debug) {
121835
+ log.error("\u274C makePayment pre-send simulation reverted", { error: label });
121836
+ }
121837
+ return {
121838
+ success: false,
121839
+ error: `Payment would revert (pre-send simulation): ${label}`,
121840
+ positionId: request.positionId,
121841
+ paymentAmountUCD: request.paymentAmount
121842
+ };
121522
121843
  }
121523
121844
  }
121845
+ const MAKE_PAYMENT_GAS_CEILING = 500000n;
121846
+ const tx = await sendEip1559Transaction({
121847
+ signer: paymentSigner,
121848
+ to: paymentContractAddress,
121849
+ data: paymentCalldata,
121850
+ gasLimit: MAKE_PAYMENT_GAS_CEILING
121851
+ });
121852
+ if (this.config.debug) {
121853
+ log.info("\u2705 makePayment broadcast", { txHash: tx.hash });
121854
+ }
121524
121855
  if (this.config.debug) {
121525
121856
  log.info(`\u{1F4E4} Transaction sent: ${tx.hash}`);
121526
121857
  log.info("\u23F3 Waiting for transaction confirmation...");
@@ -121664,9 +121995,6 @@ Error data: ${errorData || "none"}`
121664
121995
  result.effectiveGasPrice = receipt.effectiveGasPrice?.toString() || receipt.gasPrice?.toString();
121665
121996
  }
121666
121997
  return result;
121667
- } finally {
121668
- this.invalidateCachesForPosition(request.positionId);
121669
- this.releaseWriteLock(request.positionId);
121670
121998
  }
121671
121999
  }
121672
122000
  /**
@@ -122228,6 +122556,7 @@ Error data: ${errorData || "none"}`
122228
122556
  utxoVout: withdrawalParams.utxoVout
122229
122557
  });
122230
122558
  }
122559
+ await awaitSafeSubmissionWindow(Number(withdrawalParams.quantumTimestamp));
122231
122560
  let tx;
122232
122561
  try {
122233
122562
  tx = await positionManagerContract["withdrawBTC"](
@@ -124449,7 +124778,7 @@ export {
124449
124778
  ErrorSeverity,
124450
124779
  EventHelpers,
124451
124780
  LOCALHOST_CONTRACTS,
124452
- Cache as LRUCache,
124781
+ LRUCache,
124453
124782
  LoanCreator,
124454
124783
  LoanQuery,
124455
124784
  LoanStatus,