@gvnrdao/dh-sdk 0.0.293 → 0.0.295

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
@@ -108002,8 +108002,11 @@ async function resolveEip1559FeeFields(provider) {
108002
108002
  let maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? void 0;
108003
108003
  if (!maxFeePerGas || !maxPriorityFeePerGas) {
108004
108004
  const gasPrice = (await provider.getFeeData()).gasPrice ?? parseUnits("1", "gwei");
108005
- maxFeePerGas = maxFeePerGas ?? gasPrice * 2n;
108006
- maxPriorityFeePerGas = maxPriorityFeePerGas ?? parseUnits("1.5", "gwei");
108005
+ maxFeePerGas = maxFeePerGas || gasPrice * 2n;
108006
+ maxPriorityFeePerGas = maxPriorityFeePerGas || parseUnits("1.5", "gwei");
108007
+ }
108008
+ if (maxFeePerGas < maxPriorityFeePerGas) {
108009
+ maxFeePerGas = maxPriorityFeePerGas;
108007
108010
  }
108008
108011
  return { maxFeePerGas, maxPriorityFeePerGas };
108009
108012
  }
@@ -108108,6 +108111,9 @@ function validateSDKConfig(config) {
108108
108111
 
108109
108112
  // src/utils/quantum-timing.ts
108110
108113
  var QUANTUM_WINDOW_SECONDS = 60;
108114
+ var DEAD_ZONE_SECONDS = 8;
108115
+ var INCLUSION_LATENCY_BUDGET = 16;
108116
+ var POST_BOUNDARY_SKEW_MARGIN = 3;
108111
108117
  function calculateNextQuantumTimestamp() {
108112
108118
  const now = Math.floor(Date.now() / 1e3);
108113
108119
  const currentQuantum = Math.floor(now / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -108136,6 +108142,27 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
108136
108142
  console.log(" Current quantum:", currentQuantum);
108137
108143
  console.log(" Valid window: PAST/CURRENT/NEXT (180s total)");
108138
108144
  }
108145
+ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
108146
+ const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
108147
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
108148
+ const nowSec = now();
108149
+ const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108150
+ const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108151
+ const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
108152
+ const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
108153
+ if (isNextQuantumSig && secsToBoundary <= DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET) {
108154
+ const target = currentQuantum + QUANTUM_WINDOW_SECONDS + POST_BOUNDARY_SKEW_MARGIN;
108155
+ const waitSeconds = target - nowSec;
108156
+ if (waitSeconds > 0) {
108157
+ console.log(
108158
+ `[Quantum Timing] \u23F3 Deferring send ${waitSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, current ${currentQuantum}, ${secsToBoundary}s to boundary)`
108159
+ );
108160
+ await sleep(waitSeconds * 1e3);
108161
+ return { waited: true, waitedSeconds: waitSeconds };
108162
+ }
108163
+ }
108164
+ return { waited: false, waitedSeconds: 0 };
108165
+ }
108139
108166
 
108140
108167
  // src/utils/mint-authorization.utils.ts
108141
108168
  var PKP_NFT_ABI = [
@@ -109745,106 +109772,365 @@ function createContractManager(config) {
109745
109772
  }
109746
109773
 
109747
109774
  // src/modules/cache/cache-manager.module.ts
109748
- var Cache = class {
109749
- cache = /* @__PURE__ */ new Map();
109775
+ var LRUCache = class {
109776
+ cache;
109777
+ /**
109778
+ * Audit M-J: singleflight registry for `getOrCompute` / `getOrComputeResult`.
109779
+ * Concurrent cache-miss callers for the same key share one inflight promise
109780
+ * instead of each running `compute()` independently — important when the
109781
+ * compute spends a paid LIT capacity credit or hits a rate-limited upstream.
109782
+ */
109783
+ inflight = /* @__PURE__ */ new Map();
109784
+ inflightResult = /* @__PURE__ */ new Map();
109750
109785
  maxSize;
109751
109786
  ttlMs;
109752
- constructor(config) {
109753
- this.maxSize = config.maxSize;
109754
- this.ttlMs = config.ttlMs;
109787
+ debug;
109788
+ name;
109789
+ // Statistics
109790
+ stats = {
109791
+ hits: 0,
109792
+ misses: 0,
109793
+ evictions: 0
109794
+ };
109795
+ constructor(config = {}) {
109796
+ this.cache = /* @__PURE__ */ new Map();
109797
+ this.maxSize = config.maxSize || 1e3;
109798
+ this.ttlMs = config.ttlMs || 6e4;
109799
+ this.debug = config.debug || false;
109800
+ this.name = config.name || "Cache";
109801
+ if (this.debug) {
109802
+ console.log(
109803
+ `\u{1F4BE} [${this.name}] Initialized: maxSize=${this.maxSize}, ttl=${this.ttlMs}ms`
109804
+ );
109805
+ }
109755
109806
  }
109756
109807
  /**
109757
109808
  * Get value from cache
109809
+ *
109810
+ * Returns null if:
109811
+ * - Key not found
109812
+ * - Entry has expired
109813
+ *
109814
+ * @param key - Cache key
109815
+ * @returns Cached value or null
109758
109816
  */
109759
109817
  get(key) {
109760
109818
  const entry = this.cache.get(key);
109761
109819
  if (!entry) {
109762
- return void 0;
109820
+ this.stats.misses++;
109821
+ if (this.debug) {
109822
+ console.log(`\u274C [${this.name}] Cache MISS: ${String(key)}`);
109823
+ }
109824
+ return null;
109763
109825
  }
109764
- const now = Date.now();
109765
- if (now - entry.timestamp > entry.ttl) {
109826
+ if (this.isExpired(entry)) {
109766
109827
  this.cache.delete(key);
109767
- return void 0;
109828
+ this.stats.misses++;
109829
+ if (this.debug) {
109830
+ const age = Date.now() - entry.timestamp;
109831
+ console.log(`\u23F0 [${this.name}] Cache EXPIRED: ${String(key)} (age: ${age}ms)`);
109832
+ }
109833
+ return null;
109834
+ }
109835
+ entry.hits++;
109836
+ entry.lastAccessed = Date.now();
109837
+ this.cache.set(key, entry);
109838
+ this.stats.hits++;
109839
+ if (this.debug) {
109840
+ const age = Date.now() - entry.timestamp;
109841
+ console.log(
109842
+ `\u2705 [${this.name}] Cache HIT: ${String(key)} (age: ${age}ms, hits: ${entry.hits})`
109843
+ );
109768
109844
  }
109769
109845
  return entry.value;
109770
109846
  }
109847
+ /**
109848
+ * Get value from cache with Result wrapper
109849
+ *
109850
+ * Useful when you want to distinguish between "not found" and "expired"
109851
+ */
109852
+ getResult(key) {
109853
+ const value = this.get(key);
109854
+ if (value === null) {
109855
+ return failure(
109856
+ new SDKError({
109857
+ message: `Cache miss for key: ${String(key)}`,
109858
+ category: "CACHE" /* CACHE */,
109859
+ severity: "LOW" /* LOW */,
109860
+ originalError: new Error("Cache miss")
109861
+ })
109862
+ );
109863
+ }
109864
+ return success(value);
109865
+ }
109771
109866
  /**
109772
109867
  * Set value in cache
109868
+ *
109869
+ * If cache is full, evicts the least recently used entry
109870
+ *
109871
+ * @param key - Cache key
109872
+ * @param value - Value to cache
109873
+ * @param ttl - Optional custom TTL for this entry (ms)
109773
109874
  */
109774
109875
  set(key, value, ttl) {
109775
109876
  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
- }
109877
+ this.evictLRU();
109780
109878
  }
109781
- this.cache.set(key, {
109879
+ const entry = {
109782
109880
  value,
109783
109881
  timestamp: Date.now(),
109784
- ttl: ttl || this.ttlMs
109785
- });
109882
+ hits: 0,
109883
+ lastAccessed: Date.now()
109884
+ };
109885
+ this.cache.set(key, entry);
109886
+ if (this.debug) {
109887
+ const effectiveTtl = ttl || this.ttlMs;
109888
+ console.log(
109889
+ `\u{1F4BE} [${this.name}] Cache SET: ${String(key)} (ttl: ${effectiveTtl}ms, size: ${this.cache.size}/${this.maxSize})`
109890
+ );
109891
+ }
109786
109892
  }
109787
109893
  /**
109788
- * Check if key exists in cache
109894
+ * Set value in cache with Result wrapper
109895
+ */
109896
+ setResult(key, value, ttl) {
109897
+ try {
109898
+ this.set(key, value, ttl);
109899
+ return success(void 0);
109900
+ } catch (error2) {
109901
+ return failure(
109902
+ new SDKError({
109903
+ message: `Failed to set cache value for key: ${String(key)}`,
109904
+ category: "CACHE" /* CACHE */,
109905
+ severity: "MEDIUM" /* MEDIUM */,
109906
+ originalError: error2 instanceof Error ? error2 : new Error(String(error2))
109907
+ })
109908
+ );
109909
+ }
109910
+ }
109911
+ /**
109912
+ * Check if key exists in cache (without affecting stats)
109789
109913
  */
109790
109914
  has(key) {
109791
- return this.get(key) !== void 0;
109915
+ const entry = this.cache.get(key);
109916
+ return entry !== void 0 && !this.isExpired(entry);
109792
109917
  }
109793
109918
  /**
109794
- * Delete key from cache
109919
+ * Delete specific key from cache
109795
109920
  */
109796
109921
  delete(key) {
109797
- return this.cache.delete(key);
109922
+ const deleted = this.cache.delete(key);
109923
+ if (deleted && this.debug) {
109924
+ console.log(`\u{1F5D1}\uFE0F [${this.name}] Cache DELETE: ${String(key)}`);
109925
+ }
109926
+ return deleted;
109798
109927
  }
109799
109928
  /**
109800
- * Clear all cache entries
109929
+ * Clear entire cache
109801
109930
  */
109802
109931
  clear() {
109932
+ const previousSize = this.cache.size;
109803
109933
  this.cache.clear();
109934
+ this.stats = {
109935
+ hits: 0,
109936
+ misses: 0,
109937
+ evictions: 0
109938
+ };
109939
+ if (this.debug) {
109940
+ console.log(`\u{1F9F9} [${this.name}] Cache CLEARED: removed ${previousSize} entries`);
109941
+ }
109804
109942
  }
109805
109943
  /**
109806
- * Clean expired entries
109944
+ * Get current cache size
109945
+ */
109946
+ size() {
109947
+ return this.cache.size;
109948
+ }
109949
+ /**
109950
+ * Get cache statistics
109951
+ */
109952
+ getStats() {
109953
+ const entries = Array.from(this.cache.values());
109954
+ const timestamps = entries.map((e) => e.timestamp);
109955
+ const total = this.stats.hits + this.stats.misses;
109956
+ const hitRate = total === 0 ? 0 : this.stats.hits / total * 100;
109957
+ return {
109958
+ size: this.cache.size,
109959
+ hits: this.stats.hits,
109960
+ misses: this.stats.misses,
109961
+ evictions: this.stats.evictions,
109962
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : 0,
109963
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : 0,
109964
+ hitRate
109965
+ };
109966
+ }
109967
+ /**
109968
+ * Get hit rate percentage
109969
+ */
109970
+ getHitRate() {
109971
+ const total = this.stats.hits + this.stats.misses;
109972
+ return total === 0 ? 0 : this.stats.hits / total * 100;
109973
+ }
109974
+ /**
109975
+ * Get all cached keys (for debugging)
109976
+ */
109977
+ getKeys() {
109978
+ return Array.from(this.cache.keys());
109979
+ }
109980
+ /**
109981
+ * Get all cached values (for debugging)
109982
+ */
109983
+ getValues() {
109984
+ return Array.from(this.cache.values()).map((entry) => entry.value);
109985
+ }
109986
+ /**
109987
+ * Get all cache entries with metadata (for debugging)
109988
+ */
109989
+ getEntries() {
109990
+ return Array.from(this.cache.entries()).map(([key, entry]) => ({
109991
+ key,
109992
+ value: entry.value,
109993
+ metadata: {
109994
+ timestamp: entry.timestamp,
109995
+ hits: entry.hits,
109996
+ lastAccessed: entry.lastAccessed
109997
+ }
109998
+ }));
109999
+ }
110000
+ /**
110001
+ * Clean up expired entries
110002
+ *
110003
+ * Useful for periodic maintenance
110004
+ *
110005
+ * @returns Number of entries cleaned
109807
110006
  */
109808
110007
  cleanExpired() {
109809
110008
  const now = Date.now();
109810
- let cleaned = 0;
110009
+ let cleanedCount = 0;
109811
110010
  for (const [key, entry] of this.cache.entries()) {
109812
- if (now - entry.timestamp > entry.ttl) {
110011
+ if (now - entry.timestamp > this.ttlMs) {
109813
110012
  this.cache.delete(key);
109814
- cleaned++;
110013
+ cleanedCount++;
109815
110014
  }
109816
110015
  }
109817
- return cleaned;
110016
+ if (cleanedCount > 0 && this.debug) {
110017
+ console.log(`\u{1F9F9} [${this.name}] Cleaned ${cleanedCount} expired entries`);
110018
+ }
110019
+ return cleanedCount;
109818
110020
  }
109819
110021
  /**
109820
- * Get cache statistics
110022
+ * Check if cache entry is expired
109821
110023
  */
109822
- getStats() {
109823
- return {
109824
- size: this.cache.size,
109825
- maxSize: this.maxSize,
109826
- ttlMs: this.ttlMs
109827
- };
110024
+ isExpired(entry) {
110025
+ return Date.now() - entry.timestamp > this.ttlMs;
110026
+ }
110027
+ /**
110028
+ * Evict least recently used entry
110029
+ */
110030
+ evictLRU() {
110031
+ let oldestKey = null;
110032
+ let oldestAccess = Infinity;
110033
+ for (const [key, entry] of this.cache.entries()) {
110034
+ if (entry.lastAccessed < oldestAccess) {
110035
+ oldestAccess = entry.lastAccessed;
110036
+ oldestKey = key;
110037
+ }
110038
+ }
110039
+ if (oldestKey !== null) {
110040
+ this.cache.delete(oldestKey);
110041
+ this.stats.evictions++;
110042
+ if (this.debug) {
110043
+ const timeSinceAccess = Date.now() - oldestAccess;
110044
+ console.log(
110045
+ `\u267B\uFE0F [${this.name}] Cache EVICT (LRU): ${String(oldestKey)} (last accessed: ${timeSinceAccess}ms ago)`
110046
+ );
110047
+ }
110048
+ }
110049
+ }
110050
+ /**
110051
+ * Get or compute value
110052
+ *
110053
+ * If key exists in cache, returns cached value.
110054
+ * Otherwise, computes value using provided function and caches it.
110055
+ *
110056
+ * @param key - Cache key
110057
+ * @param compute - Function to compute value if not in cache
110058
+ * @param ttl - Optional custom TTL for this entry
110059
+ * @returns Cached or computed value
110060
+ */
110061
+ async getOrCompute(key, compute, ttl) {
110062
+ const cached = this.get(key);
110063
+ if (cached !== null) {
110064
+ return cached;
110065
+ }
110066
+ const existing = this.inflight.get(key);
110067
+ if (existing) {
110068
+ return existing;
110069
+ }
110070
+ const computePromise = (async () => {
110071
+ try {
110072
+ const value = await compute();
110073
+ this.set(key, value, ttl);
110074
+ return value;
110075
+ } finally {
110076
+ this.inflight.delete(key);
110077
+ }
110078
+ })();
110079
+ this.inflight.set(key, computePromise);
110080
+ return computePromise;
110081
+ }
110082
+ /**
110083
+ * Get or compute value with Result wrapper
110084
+ */
110085
+ async getOrComputeResult(key, compute, ttl) {
110086
+ const cached = this.get(key);
110087
+ if (cached !== null) {
110088
+ return success(cached);
110089
+ }
110090
+ const existing = this.inflightResult.get(key);
110091
+ if (existing) {
110092
+ return existing;
110093
+ }
110094
+ const computePromise = (async () => {
110095
+ try {
110096
+ return await compute();
110097
+ } finally {
110098
+ this.inflightResult.delete(key);
110099
+ }
110100
+ })();
110101
+ this.inflightResult.set(key, computePromise);
110102
+ const result = await computePromise;
110103
+ if (result.success) {
110104
+ this.set(key, result.value, ttl);
110105
+ }
110106
+ return result;
109828
110107
  }
109829
110108
  };
109830
110109
  var CacheManager = class {
109831
110110
  caches = /* @__PURE__ */ new Map();
109832
- debug;
109833
- constructor(config = {}) {
109834
- this.debug = config.debug || false;
110111
+ globalConfig;
110112
+ constructor(globalConfig = {}) {
110113
+ this.globalConfig = globalConfig;
109835
110114
  }
109836
110115
  /**
109837
- * Get or create a cache instance
110116
+ * Create or get a named cache
110117
+ *
110118
+ * @param name - Unique cache name
110119
+ * @param config - Optional cache-specific configuration
110120
+ * @returns LRU cache instance
109838
110121
  */
109839
110122
  getCache(name, config) {
109840
- if (this.caches.has(name)) {
109841
- return this.caches.get(name);
110123
+ const existingCache = this.caches.get(name);
110124
+ if (existingCache) {
110125
+ return existingCache;
109842
110126
  }
109843
- const cache = new Cache(config);
110127
+ const mergedConfig = {
110128
+ ...this.globalConfig,
110129
+ ...config,
110130
+ name
110131
+ };
110132
+ const cache = new LRUCache(mergedConfig);
109844
110133
  this.caches.set(name, cache);
109845
- if (this.debug) {
109846
- console.log(`[CacheManager] Created cache: ${name}`, config);
109847
- }
109848
110134
  return cache;
109849
110135
  }
109850
110136
  /**
@@ -109859,11 +110145,11 @@ var CacheManager = class {
109859
110145
  * Clean expired entries from all caches
109860
110146
  */
109861
110147
  cleanAllExpired() {
109862
- let total = 0;
110148
+ let totalCleaned = 0;
109863
110149
  for (const cache of this.caches.values()) {
109864
- total += cache.cleanExpired();
110150
+ totalCleaned += cache.cleanExpired();
109865
110151
  }
109866
- return total;
110152
+ return totalCleaned;
109867
110153
  }
109868
110154
  /**
109869
110155
  * Get statistics for all caches
@@ -109876,11 +110162,16 @@ var CacheManager = class {
109876
110162
  return stats;
109877
110163
  }
109878
110164
  /**
109879
- * Destroy cache manager
110165
+ * Get list of all cache names
109880
110166
  */
109881
- destroy() {
109882
- this.clearAll();
109883
- this.caches.clear();
110167
+ getCacheNames() {
110168
+ return Array.from(this.caches.keys());
110169
+ }
110170
+ /**
110171
+ * Delete a named cache
110172
+ */
110173
+ deleteCache(name) {
110174
+ return this.caches.delete(name);
109884
110175
  }
109885
110176
  };
109886
110177
  function createCacheManager(config) {
@@ -118373,12 +118664,11 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118373
118664
  {}
118374
118665
  );
118375
118666
  }
118376
- try {
118377
- return await this._requestMintUCDAttempt(request);
118378
- } catch (error2) {
118379
- const errorMsg = error2.message || String(error2);
118667
+ const result = await this._requestMintUCDAttempt(request);
118668
+ if (!result.success && fullRetry < MAX_FULL_RETRIES) {
118669
+ const errorMsg = result.error || "";
118380
118670
  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) {
118671
+ if (isStaleTimestamp) {
118382
118672
  if (this.config.debug) {
118383
118673
  log.warn(
118384
118674
  `\u26A0\uFE0F Timestamp became stale during mint process. Retrying from beginning...`,
@@ -118388,8 +118678,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118388
118678
  await new Promise((resolve) => setTimeout(resolve, 2e3));
118389
118679
  continue;
118390
118680
  }
118391
- throw error2;
118392
118681
  }
118682
+ return result;
118393
118683
  }
118394
118684
  return {
118395
118685
  success: false,
@@ -119011,6 +119301,7 @@ Context: Quantum timestamp=${validationResponse.timestamp}, Position=${request.p
119011
119301
  "0x131d9a21": "QuantumExpired()",
119012
119302
  "0x52ce5d58": "QuantumAlreadyUsed()",
119013
119303
  "0x137f3b70": "InDeadZone()",
119304
+ "0xbe4b82c1": "DeadZoneViolation()",
119014
119305
  "0x3e76a3c9": "QuantumOutsideWindow()",
119015
119306
  "0x62278171": "InvalidValidatorSignature()",
119016
119307
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119031,7 +119322,7 @@ Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMi
119031
119322
  Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
119032
119323
  ` : "";
119033
119324
  let quantumContext = "";
119034
- if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0x3e76a3c9") {
119325
+ if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0xbe4b82c1" || selector === "0x3e76a3c9") {
119035
119326
  const currentTime2 = Math.floor(Date.now() / 1e3);
119036
119327
  const currentQuantum = Math.floor(currentTime2 / 60) * 60;
119037
119328
  const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
@@ -119293,6 +119584,7 @@ Position: ${request.positionId}`
119293
119584
  validationResponse.timestamp,
119294
119585
  signatureHexMint
119295
119586
  ]);
119587
+ await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
119296
119588
  const fromAddress = await signer.getAddress();
119297
119589
  const estimatedGas = await estimateContractCallGasWithMargin(
119298
119590
  signerProvider,
@@ -119351,6 +119643,7 @@ Position: ${request.positionId}`
119351
119643
  "0x131d9a21": "QuantumExpired()",
119352
119644
  "0x52ce5d58": "QuantumAlreadyUsed()",
119353
119645
  "0x137f3b70": "InDeadZone()",
119646
+ "0xbe4b82c1": "DeadZoneViolation()",
119354
119647
  "0x3e76a3c9": "QuantumOutsideWindow()",
119355
119648
  "0x62278171": "InvalidValidatorSignature()",
119356
119649
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119530,6 +119823,7 @@ Error data: ${errorData || "none"}`
119530
119823
  "0x131d9a21": "QuantumExpired() - Signature quantum window has closed",
119531
119824
  "0x52ce5d58": "QuantumAlreadyUsed() - This quantum was already used for this position",
119532
119825
  "0x137f3b70": "InDeadZone() - Timestamp in dead zone (near quantum boundary)",
119826
+ "0xbe4b82c1": "DeadZoneViolation() - Non-current-quantum signature mined in the last 8s of the current quantum",
119533
119827
  "0x3e76a3c9": "QuantumOutsideWindow() - Timestamp not in past/current/next quantum window"
119534
119828
  };
119535
119829
  const errorName = knownErrors[selector] || `Unknown error ${selector}`;
@@ -120598,6 +120892,7 @@ Error data: ${errorData || "none"}`
120598
120892
  );
120599
120893
  }
120600
120894
  const positionManager = positionManagerResult.value;
120895
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
120601
120896
  const tx = await positionManager["extendPosition"](
120602
120897
  positionIdBytes32,
120603
120898
  BigInt(_selectedTerm),
@@ -121030,6 +121325,57 @@ Error data: ${errorData || "none"}`
121030
121325
  if (!pauseCheck.ok) {
121031
121326
  return { success: false, error: pauseCheck.error };
121032
121327
  }
121328
+ const MAX_FULL_RETRIES = 3;
121329
+ for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
121330
+ if (fullRetry > 1 && this.config.debug) {
121331
+ log.info(
121332
+ `\u{1F504} Full payment retry ${fullRetry}/${MAX_FULL_RETRIES} due to quantum-timing failure...`,
121333
+ {}
121334
+ );
121335
+ }
121336
+ const result = await this._makePaymentAttempt(request);
121337
+ if (fullRetry < MAX_FULL_RETRIES && this.isRetryablePaymentQuantumFailure(result)) {
121338
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
121339
+ continue;
121340
+ }
121341
+ return result;
121342
+ }
121343
+ return {
121344
+ success: false,
121345
+ error: "Max retries exceeded for payment operation"
121346
+ };
121347
+ } finally {
121348
+ this.invalidateCachesForPosition(request.positionId);
121349
+ this.releaseWriteLock(request.positionId);
121350
+ }
121351
+ }
121352
+ /**
121353
+ * Whether a failed payment attempt is a quantum-timing failure that a fresh
121354
+ * re-sign can fix (safe to retry), versus a terminal failure.
121355
+ *
121356
+ * Retryable: pre-send simulation quantum errors, or an atomic mined revert
121357
+ * (status 0) — no funds moved and the quantum was not recorded on-chain, so a
121358
+ * fresh signature + resubmit is safe.
121359
+ *
121360
+ * NOT retryable: a confirmation timeout — the original tx may still be pending, so
121361
+ * a resubmit could double-pay. It is surfaced (with its tx hash) instead.
121362
+ */
121363
+ isRetryablePaymentQuantumFailure(result) {
121364
+ if (result.success || !result.error)
121365
+ return false;
121366
+ const e = result.error;
121367
+ if (e.includes("Transaction timeout"))
121368
+ return false;
121369
+ return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
121370
+ }
121371
+ /**
121372
+ * One payment attempt: user auth → Lit Action authorization → dead-zone gate →
121373
+ * pre-send simulation → broadcast → confirmation. Always resolves to a
121374
+ * PartialPaymentResult (never throws to the caller); the makePayment wrapper owns
121375
+ * the write lock, the pause pre-check, and the bounded re-sign retry loop.
121376
+ */
121377
+ async _makePaymentAttempt(request) {
121378
+ try {
121033
121379
  if (this.config.debug) {
121034
121380
  log.info(`\u{1F4B3} Making payment...`, {});
121035
121381
  log.info(` Request object:`, { request });
@@ -121290,7 +121636,6 @@ Error data: ${errorData || "none"}`
121290
121636
  error: `Failed to get PositionManager: ${positionManagerResult.error.message}`
121291
121637
  };
121292
121638
  }
121293
- const positionManager = positionManagerResult.value;
121294
121639
  if (this.config.debug) {
121295
121640
  log.info(
121296
121641
  ` About to call toBytes32 with: ${request.positionId} (type: ${typeof request.positionId})`
@@ -121350,9 +121695,6 @@ Error data: ${errorData || "none"}`
121350
121695
  currentQuantum
121351
121696
  });
121352
121697
  }
121353
- if (this.config.debug) {
121354
- log.info("\u23ED\uFE0F Skipping dead zone check to isolate BigNumber issue", {});
121355
- }
121356
121698
  if (this.config.debug) {
121357
121699
  log.info("\u{1F50D} Final timestamp validation:", {
121358
121700
  litActionTimestamp: litActionResult.timestamp,
@@ -121419,108 +121761,100 @@ Error data: ${errorData || "none"}`
121419
121761
  quantumTimestamp: quantumTimestamp.toString()
121420
121762
  });
121421
121763
  }
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)
121764
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121765
+ const paymentAmountStr = paymentAmountWei.toString();
121766
+ const quantumTimestampStr = quantumTimestamp.toString();
121767
+ const btcPriceStr = btcPrice.toString();
121768
+ const paymentContractAddress = this.getContractAddressesOrThrow().positionManager;
121769
+ const paymentSigner = this.getSignerOrThrow();
121770
+ const paymentFrom = await paymentSigner.getAddress();
121771
+ const paymentProvider = contractManager.getProvider();
121772
+ const paymentIface = new Interface([
121773
+ "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121774
+ ]);
121775
+ const paymentCalldata = paymentIface.encodeFunctionData("makePayment", [
121776
+ positionIdBytes32,
121777
+ paymentAmountStr,
121778
+ quantumTimestampStr,
121779
+ btcPriceStr,
121780
+ signature
121781
+ ]);
121782
+ const decodePaymentRevert = (e) => {
121783
+ const raw = e?.data ?? e?.info?.error?.data ?? e?.error?.data?.data ?? e?.error?.data;
121784
+ const data = typeof raw === "string" ? raw : void 0;
121785
+ if (!data || !data.startsWith("0x") || data.length < 10)
121786
+ return null;
121787
+ const selector = data.slice(0, 10);
121788
+ const map3 = {
121789
+ "0xbe4b82c1": "DeadZoneViolation()",
121790
+ "0x137f3b70": "InDeadZone()",
121791
+ "0x52ce5d58": "QuantumAlreadyUsed()",
121792
+ "0x3e76a3c9": "QuantumOutsideWindow()",
121793
+ "0x131d9a21": "QuantumExpired()",
121794
+ "0x8baa579f": "InvalidSignature()",
121795
+ "0x62278171": "InvalidValidatorSignature()",
121796
+ "0x3ee5aeb5": "OperationNotAuthorized()",
121797
+ "0x48f5c3ed": "Unauthorized()"
121798
+ };
121799
+ return map3[selector] ?? `Unknown error ${selector}`;
121800
+ };
121801
+ const MAX_DEADZONE_RESIMULATIONS = 3;
121802
+ for (let sim = 1; ; sim++) {
121803
+ try {
121804
+ await paymentProvider.call({
121805
+ to: paymentContractAddress,
121806
+ from: paymentFrom,
121807
+ data: paymentCalldata
121479
121808
  });
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)
121809
+ break;
121810
+ } catch (simError) {
121811
+ const decoded = decodePaymentRevert(simError);
121812
+ if (decoded === "DeadZoneViolation()" && sim <= MAX_DEADZONE_RESIMULATIONS) {
121813
+ const nowSec = Math.floor(Date.now() / 1e3);
121814
+ const sigQuantum = Math.floor(Number(quantumTimestamp) / 60) * 60;
121815
+ const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
121816
+ if (sigQuantum === realCurrentQuantum) {
121817
+ if (this.config.debug) {
121818
+ log.info(
121819
+ "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
121820
+ {}
121821
+ );
121822
+ }
121823
+ break;
121497
121824
  }
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
- });
121825
+ const gate = await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121826
+ if (gate.waited) {
121827
+ if (this.config.debug) {
121828
+ log.info(
121829
+ `\u{1F501} Re-simulating makePayment after crossing the quantum boundary (attempt ${sim}/${MAX_DEADZONE_RESIMULATIONS})`,
121830
+ {}
121831
+ );
121832
+ }
121833
+ continue;
121834
+ }
121835
+ }
121836
+ const label = decoded ?? simError?.reason ?? simError?.shortMessage ?? (simError instanceof Error ? simError.message : String(simError));
121837
+ if (this.config.debug) {
121838
+ log.error("\u274C makePayment pre-send simulation reverted", { error: label });
121839
+ }
121840
+ return {
121841
+ success: false,
121842
+ error: `Payment would revert (pre-send simulation): ${label}`,
121843
+ positionId: request.positionId,
121844
+ paymentAmountUCD: request.paymentAmount
121845
+ };
121522
121846
  }
121523
121847
  }
121848
+ const MAKE_PAYMENT_GAS_CEILING = 500000n;
121849
+ const tx = await sendEip1559Transaction({
121850
+ signer: paymentSigner,
121851
+ to: paymentContractAddress,
121852
+ data: paymentCalldata,
121853
+ gasLimit: MAKE_PAYMENT_GAS_CEILING
121854
+ });
121855
+ if (this.config.debug) {
121856
+ log.info("\u2705 makePayment broadcast", { txHash: tx.hash });
121857
+ }
121524
121858
  if (this.config.debug) {
121525
121859
  log.info(`\u{1F4E4} Transaction sent: ${tx.hash}`);
121526
121860
  log.info("\u23F3 Waiting for transaction confirmation...");
@@ -121664,9 +121998,6 @@ Error data: ${errorData || "none"}`
121664
121998
  result.effectiveGasPrice = receipt.effectiveGasPrice?.toString() || receipt.gasPrice?.toString();
121665
121999
  }
121666
122000
  return result;
121667
- } finally {
121668
- this.invalidateCachesForPosition(request.positionId);
121669
- this.releaseWriteLock(request.positionId);
121670
122001
  }
121671
122002
  }
121672
122003
  /**
@@ -122228,6 +122559,7 @@ Error data: ${errorData || "none"}`
122228
122559
  utxoVout: withdrawalParams.utxoVout
122229
122560
  });
122230
122561
  }
122562
+ await awaitSafeSubmissionWindow(Number(withdrawalParams.quantumTimestamp));
122231
122563
  let tx;
122232
122564
  try {
122233
122565
  tx = await positionManagerContract["withdrawBTC"](
@@ -124449,7 +124781,7 @@ export {
124449
124781
  ErrorSeverity,
124450
124782
  EventHelpers,
124451
124783
  LOCALHOST_CONTRACTS,
124452
- Cache as LRUCache,
124784
+ LRUCache,
124453
124785
  LoanCreator,
124454
124786
  LoanQuery,
124455
124787
  LoanStatus,