@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.js CHANGED
@@ -3744,7 +3744,7 @@ var require_abstract_coder = __commonJS({
3744
3744
  function toObject2(names2, items, deep) {
3745
3745
  if (names2.indexOf(null) >= 0) {
3746
3746
  return items.map((item, index) => {
3747
- if (item instanceof Result10) {
3747
+ if (item instanceof Result11) {
3748
3748
  return toObject2(getNames2(item), item, deep);
3749
3749
  }
3750
3750
  return item;
@@ -3753,7 +3753,7 @@ var require_abstract_coder = __commonJS({
3753
3753
  return names2.reduce((accum, name, index) => {
3754
3754
  let item = items.getValue(name);
3755
3755
  if (!(name in accum)) {
3756
- if (deep && item instanceof Result10) {
3756
+ if (deep && item instanceof Result11) {
3757
3757
  item = toObject2(getNames2(item), item, deep);
3758
3758
  }
3759
3759
  accum[name] = item;
@@ -3761,7 +3761,7 @@ var require_abstract_coder = __commonJS({
3761
3761
  return accum;
3762
3762
  }, {});
3763
3763
  }
3764
- var Result10 = class _Result extends Array {
3764
+ var Result11 = class _Result extends Array {
3765
3765
  // No longer used; but cannot be removed as it will remove the
3766
3766
  // #private field from the .d.ts which may break backwards
3767
3767
  // compatibility
@@ -3965,7 +3965,7 @@ var require_abstract_coder = __commonJS({
3965
3965
  return new _Result(_guard5, items, keys);
3966
3966
  }
3967
3967
  };
3968
- exports2.Result = Result10;
3968
+ exports2.Result = Result11;
3969
3969
  function checkResultErrors2(result) {
3970
3970
  const errors2 = [];
3971
3971
  const checkErrors = function(path2, object2) {
@@ -32403,8 +32403,8 @@ ${errorReport}`);
32403
32403
  var UTXO_QUERY_MAX_RETRIES = 3;
32404
32404
  var UTXO_QUERY_RETRY_DELAY_MS = 500;
32405
32405
  var QUANTUM_WINDOW_SECONDS2 = 60;
32406
- var DEAD_ZONE_SECONDS = 16;
32407
- var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS;
32406
+ var DEAD_ZONE_SECONDS2 = 16;
32407
+ var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS2;
32408
32408
  var SECONDS_PER_DAY = 86400;
32409
32409
  var DAYS_PER_MONTH = 30;
32410
32410
  var LIT_ACTION_ETH_RPC_TIMEOUT_MS = 8e3;
@@ -34943,8 +34943,8 @@ function getMainnetConfig() {
34943
34943
  bitcoinWithdrawalAddressRegistry: MAINNET_CONTRACTS.BitcoinWithdrawalAddressRegistry || ""
34944
34944
  },
34945
34945
  subgraphs: {
34946
- diamondHandsUrl: "https://api.studio.thegraph.com/query/1755201/diamond-hands/v1.0.0-mainnet"
34947
- // Mainnet subgraph (keyless studio endpoint, browser-queryable)
34946
+ diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
34947
+ // Published Sepolia subgraph on Arbitrum One
34948
34948
  },
34949
34949
  litNetwork: "chipotle",
34950
34950
  debug: false
@@ -90299,7 +90299,7 @@ __export(src_exports, {
90299
90299
  ErrorSeverity: () => ErrorSeverity,
90300
90300
  EventHelpers: () => EventHelpers,
90301
90301
  LOCALHOST_CONTRACTS: () => LOCALHOST_CONTRACTS,
90302
- LRUCache: () => Cache,
90302
+ LRUCache: () => LRUCache,
90303
90303
  LoanCreator: () => LoanCreator,
90304
90304
  LoanQuery: () => LoanQuery,
90305
90305
  LoanStatus: () => LoanStatus,
@@ -108186,6 +108186,9 @@ function validateSDKConfig(config) {
108186
108186
 
108187
108187
  // src/utils/quantum-timing.ts
108188
108188
  var QUANTUM_WINDOW_SECONDS = 60;
108189
+ var DEAD_ZONE_SECONDS = 8;
108190
+ var INCLUSION_LATENCY_BUDGET = 16;
108191
+ var POST_BOUNDARY_SKEW_MARGIN = 3;
108189
108192
  function calculateNextQuantumTimestamp() {
108190
108193
  const now = Math.floor(Date.now() / 1e3);
108191
108194
  const currentQuantum = Math.floor(now / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -108214,6 +108217,27 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
108214
108217
  console.log(" Current quantum:", currentQuantum);
108215
108218
  console.log(" Valid window: PAST/CURRENT/NEXT (180s total)");
108216
108219
  }
108220
+ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
108221
+ const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
108222
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
108223
+ const nowSec = now();
108224
+ const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108225
+ const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108226
+ const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
108227
+ const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
108228
+ if (isNextQuantumSig && secsToBoundary <= DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET) {
108229
+ const target = currentQuantum + QUANTUM_WINDOW_SECONDS + POST_BOUNDARY_SKEW_MARGIN;
108230
+ const waitSeconds = target - nowSec;
108231
+ if (waitSeconds > 0) {
108232
+ console.log(
108233
+ `[Quantum Timing] \u23F3 Deferring send ${waitSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, current ${currentQuantum}, ${secsToBoundary}s to boundary)`
108234
+ );
108235
+ await sleep(waitSeconds * 1e3);
108236
+ return { waited: true, waitedSeconds: waitSeconds };
108237
+ }
108238
+ }
108239
+ return { waited: false, waitedSeconds: 0 };
108240
+ }
108217
108241
 
108218
108242
  // src/utils/mint-authorization.utils.ts
108219
108243
  var PKP_NFT_ABI = [
@@ -109823,106 +109847,365 @@ function createContractManager(config) {
109823
109847
  }
109824
109848
 
109825
109849
  // src/modules/cache/cache-manager.module.ts
109826
- var Cache = class {
109827
- cache = /* @__PURE__ */ new Map();
109850
+ var LRUCache = class {
109851
+ cache;
109852
+ /**
109853
+ * Audit M-J: singleflight registry for `getOrCompute` / `getOrComputeResult`.
109854
+ * Concurrent cache-miss callers for the same key share one inflight promise
109855
+ * instead of each running `compute()` independently — important when the
109856
+ * compute spends a paid LIT capacity credit or hits a rate-limited upstream.
109857
+ */
109858
+ inflight = /* @__PURE__ */ new Map();
109859
+ inflightResult = /* @__PURE__ */ new Map();
109828
109860
  maxSize;
109829
109861
  ttlMs;
109830
- constructor(config) {
109831
- this.maxSize = config.maxSize;
109832
- this.ttlMs = config.ttlMs;
109862
+ debug;
109863
+ name;
109864
+ // Statistics
109865
+ stats = {
109866
+ hits: 0,
109867
+ misses: 0,
109868
+ evictions: 0
109869
+ };
109870
+ constructor(config = {}) {
109871
+ this.cache = /* @__PURE__ */ new Map();
109872
+ this.maxSize = config.maxSize || 1e3;
109873
+ this.ttlMs = config.ttlMs || 6e4;
109874
+ this.debug = config.debug || false;
109875
+ this.name = config.name || "Cache";
109876
+ if (this.debug) {
109877
+ console.log(
109878
+ `\u{1F4BE} [${this.name}] Initialized: maxSize=${this.maxSize}, ttl=${this.ttlMs}ms`
109879
+ );
109880
+ }
109833
109881
  }
109834
109882
  /**
109835
109883
  * Get value from cache
109884
+ *
109885
+ * Returns null if:
109886
+ * - Key not found
109887
+ * - Entry has expired
109888
+ *
109889
+ * @param key - Cache key
109890
+ * @returns Cached value or null
109836
109891
  */
109837
109892
  get(key) {
109838
109893
  const entry = this.cache.get(key);
109839
109894
  if (!entry) {
109840
- return void 0;
109895
+ this.stats.misses++;
109896
+ if (this.debug) {
109897
+ console.log(`\u274C [${this.name}] Cache MISS: ${String(key)}`);
109898
+ }
109899
+ return null;
109841
109900
  }
109842
- const now = Date.now();
109843
- if (now - entry.timestamp > entry.ttl) {
109901
+ if (this.isExpired(entry)) {
109844
109902
  this.cache.delete(key);
109845
- return void 0;
109903
+ this.stats.misses++;
109904
+ if (this.debug) {
109905
+ const age = Date.now() - entry.timestamp;
109906
+ console.log(`\u23F0 [${this.name}] Cache EXPIRED: ${String(key)} (age: ${age}ms)`);
109907
+ }
109908
+ return null;
109909
+ }
109910
+ entry.hits++;
109911
+ entry.lastAccessed = Date.now();
109912
+ this.cache.set(key, entry);
109913
+ this.stats.hits++;
109914
+ if (this.debug) {
109915
+ const age = Date.now() - entry.timestamp;
109916
+ console.log(
109917
+ `\u2705 [${this.name}] Cache HIT: ${String(key)} (age: ${age}ms, hits: ${entry.hits})`
109918
+ );
109846
109919
  }
109847
109920
  return entry.value;
109848
109921
  }
109922
+ /**
109923
+ * Get value from cache with Result wrapper
109924
+ *
109925
+ * Useful when you want to distinguish between "not found" and "expired"
109926
+ */
109927
+ getResult(key) {
109928
+ const value = this.get(key);
109929
+ if (value === null) {
109930
+ return failure(
109931
+ new SDKError({
109932
+ message: `Cache miss for key: ${String(key)}`,
109933
+ category: "CACHE" /* CACHE */,
109934
+ severity: "LOW" /* LOW */,
109935
+ originalError: new Error("Cache miss")
109936
+ })
109937
+ );
109938
+ }
109939
+ return success(value);
109940
+ }
109849
109941
  /**
109850
109942
  * Set value in cache
109943
+ *
109944
+ * If cache is full, evicts the least recently used entry
109945
+ *
109946
+ * @param key - Cache key
109947
+ * @param value - Value to cache
109948
+ * @param ttl - Optional custom TTL for this entry (ms)
109851
109949
  */
109852
109950
  set(key, value, ttl) {
109853
109951
  if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
109854
- const firstKey = this.cache.keys().next().value;
109855
- if (firstKey) {
109856
- this.cache.delete(firstKey);
109857
- }
109952
+ this.evictLRU();
109858
109953
  }
109859
- this.cache.set(key, {
109954
+ const entry = {
109860
109955
  value,
109861
109956
  timestamp: Date.now(),
109862
- ttl: ttl || this.ttlMs
109863
- });
109957
+ hits: 0,
109958
+ lastAccessed: Date.now()
109959
+ };
109960
+ this.cache.set(key, entry);
109961
+ if (this.debug) {
109962
+ const effectiveTtl = ttl || this.ttlMs;
109963
+ console.log(
109964
+ `\u{1F4BE} [${this.name}] Cache SET: ${String(key)} (ttl: ${effectiveTtl}ms, size: ${this.cache.size}/${this.maxSize})`
109965
+ );
109966
+ }
109864
109967
  }
109865
109968
  /**
109866
- * Check if key exists in cache
109969
+ * Set value in cache with Result wrapper
109970
+ */
109971
+ setResult(key, value, ttl) {
109972
+ try {
109973
+ this.set(key, value, ttl);
109974
+ return success(void 0);
109975
+ } catch (error2) {
109976
+ return failure(
109977
+ new SDKError({
109978
+ message: `Failed to set cache value for key: ${String(key)}`,
109979
+ category: "CACHE" /* CACHE */,
109980
+ severity: "MEDIUM" /* MEDIUM */,
109981
+ originalError: error2 instanceof Error ? error2 : new Error(String(error2))
109982
+ })
109983
+ );
109984
+ }
109985
+ }
109986
+ /**
109987
+ * Check if key exists in cache (without affecting stats)
109867
109988
  */
109868
109989
  has(key) {
109869
- return this.get(key) !== void 0;
109990
+ const entry = this.cache.get(key);
109991
+ return entry !== void 0 && !this.isExpired(entry);
109870
109992
  }
109871
109993
  /**
109872
- * Delete key from cache
109994
+ * Delete specific key from cache
109873
109995
  */
109874
109996
  delete(key) {
109875
- return this.cache.delete(key);
109997
+ const deleted = this.cache.delete(key);
109998
+ if (deleted && this.debug) {
109999
+ console.log(`\u{1F5D1}\uFE0F [${this.name}] Cache DELETE: ${String(key)}`);
110000
+ }
110001
+ return deleted;
109876
110002
  }
109877
110003
  /**
109878
- * Clear all cache entries
110004
+ * Clear entire cache
109879
110005
  */
109880
110006
  clear() {
110007
+ const previousSize = this.cache.size;
109881
110008
  this.cache.clear();
110009
+ this.stats = {
110010
+ hits: 0,
110011
+ misses: 0,
110012
+ evictions: 0
110013
+ };
110014
+ if (this.debug) {
110015
+ console.log(`\u{1F9F9} [${this.name}] Cache CLEARED: removed ${previousSize} entries`);
110016
+ }
109882
110017
  }
109883
110018
  /**
109884
- * Clean expired entries
110019
+ * Get current cache size
110020
+ */
110021
+ size() {
110022
+ return this.cache.size;
110023
+ }
110024
+ /**
110025
+ * Get cache statistics
110026
+ */
110027
+ getStats() {
110028
+ const entries = Array.from(this.cache.values());
110029
+ const timestamps = entries.map((e) => e.timestamp);
110030
+ const total = this.stats.hits + this.stats.misses;
110031
+ const hitRate = total === 0 ? 0 : this.stats.hits / total * 100;
110032
+ return {
110033
+ size: this.cache.size,
110034
+ hits: this.stats.hits,
110035
+ misses: this.stats.misses,
110036
+ evictions: this.stats.evictions,
110037
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : 0,
110038
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : 0,
110039
+ hitRate
110040
+ };
110041
+ }
110042
+ /**
110043
+ * Get hit rate percentage
110044
+ */
110045
+ getHitRate() {
110046
+ const total = this.stats.hits + this.stats.misses;
110047
+ return total === 0 ? 0 : this.stats.hits / total * 100;
110048
+ }
110049
+ /**
110050
+ * Get all cached keys (for debugging)
110051
+ */
110052
+ getKeys() {
110053
+ return Array.from(this.cache.keys());
110054
+ }
110055
+ /**
110056
+ * Get all cached values (for debugging)
110057
+ */
110058
+ getValues() {
110059
+ return Array.from(this.cache.values()).map((entry) => entry.value);
110060
+ }
110061
+ /**
110062
+ * Get all cache entries with metadata (for debugging)
110063
+ */
110064
+ getEntries() {
110065
+ return Array.from(this.cache.entries()).map(([key, entry]) => ({
110066
+ key,
110067
+ value: entry.value,
110068
+ metadata: {
110069
+ timestamp: entry.timestamp,
110070
+ hits: entry.hits,
110071
+ lastAccessed: entry.lastAccessed
110072
+ }
110073
+ }));
110074
+ }
110075
+ /**
110076
+ * Clean up expired entries
110077
+ *
110078
+ * Useful for periodic maintenance
110079
+ *
110080
+ * @returns Number of entries cleaned
109885
110081
  */
109886
110082
  cleanExpired() {
109887
110083
  const now = Date.now();
109888
- let cleaned = 0;
110084
+ let cleanedCount = 0;
109889
110085
  for (const [key, entry] of this.cache.entries()) {
109890
- if (now - entry.timestamp > entry.ttl) {
110086
+ if (now - entry.timestamp > this.ttlMs) {
109891
110087
  this.cache.delete(key);
109892
- cleaned++;
110088
+ cleanedCount++;
109893
110089
  }
109894
110090
  }
109895
- return cleaned;
110091
+ if (cleanedCount > 0 && this.debug) {
110092
+ console.log(`\u{1F9F9} [${this.name}] Cleaned ${cleanedCount} expired entries`);
110093
+ }
110094
+ return cleanedCount;
109896
110095
  }
109897
110096
  /**
109898
- * Get cache statistics
110097
+ * Check if cache entry is expired
109899
110098
  */
109900
- getStats() {
109901
- return {
109902
- size: this.cache.size,
109903
- maxSize: this.maxSize,
109904
- ttlMs: this.ttlMs
109905
- };
110099
+ isExpired(entry) {
110100
+ return Date.now() - entry.timestamp > this.ttlMs;
110101
+ }
110102
+ /**
110103
+ * Evict least recently used entry
110104
+ */
110105
+ evictLRU() {
110106
+ let oldestKey = null;
110107
+ let oldestAccess = Infinity;
110108
+ for (const [key, entry] of this.cache.entries()) {
110109
+ if (entry.lastAccessed < oldestAccess) {
110110
+ oldestAccess = entry.lastAccessed;
110111
+ oldestKey = key;
110112
+ }
110113
+ }
110114
+ if (oldestKey !== null) {
110115
+ this.cache.delete(oldestKey);
110116
+ this.stats.evictions++;
110117
+ if (this.debug) {
110118
+ const timeSinceAccess = Date.now() - oldestAccess;
110119
+ console.log(
110120
+ `\u267B\uFE0F [${this.name}] Cache EVICT (LRU): ${String(oldestKey)} (last accessed: ${timeSinceAccess}ms ago)`
110121
+ );
110122
+ }
110123
+ }
110124
+ }
110125
+ /**
110126
+ * Get or compute value
110127
+ *
110128
+ * If key exists in cache, returns cached value.
110129
+ * Otherwise, computes value using provided function and caches it.
110130
+ *
110131
+ * @param key - Cache key
110132
+ * @param compute - Function to compute value if not in cache
110133
+ * @param ttl - Optional custom TTL for this entry
110134
+ * @returns Cached or computed value
110135
+ */
110136
+ async getOrCompute(key, compute, ttl) {
110137
+ const cached = this.get(key);
110138
+ if (cached !== null) {
110139
+ return cached;
110140
+ }
110141
+ const existing = this.inflight.get(key);
110142
+ if (existing) {
110143
+ return existing;
110144
+ }
110145
+ const computePromise = (async () => {
110146
+ try {
110147
+ const value = await compute();
110148
+ this.set(key, value, ttl);
110149
+ return value;
110150
+ } finally {
110151
+ this.inflight.delete(key);
110152
+ }
110153
+ })();
110154
+ this.inflight.set(key, computePromise);
110155
+ return computePromise;
110156
+ }
110157
+ /**
110158
+ * Get or compute value with Result wrapper
110159
+ */
110160
+ async getOrComputeResult(key, compute, ttl) {
110161
+ const cached = this.get(key);
110162
+ if (cached !== null) {
110163
+ return success(cached);
110164
+ }
110165
+ const existing = this.inflightResult.get(key);
110166
+ if (existing) {
110167
+ return existing;
110168
+ }
110169
+ const computePromise = (async () => {
110170
+ try {
110171
+ return await compute();
110172
+ } finally {
110173
+ this.inflightResult.delete(key);
110174
+ }
110175
+ })();
110176
+ this.inflightResult.set(key, computePromise);
110177
+ const result = await computePromise;
110178
+ if (result.success) {
110179
+ this.set(key, result.value, ttl);
110180
+ }
110181
+ return result;
109906
110182
  }
109907
110183
  };
109908
110184
  var CacheManager = class {
109909
110185
  caches = /* @__PURE__ */ new Map();
109910
- debug;
109911
- constructor(config = {}) {
109912
- this.debug = config.debug || false;
110186
+ globalConfig;
110187
+ constructor(globalConfig = {}) {
110188
+ this.globalConfig = globalConfig;
109913
110189
  }
109914
110190
  /**
109915
- * Get or create a cache instance
110191
+ * Create or get a named cache
110192
+ *
110193
+ * @param name - Unique cache name
110194
+ * @param config - Optional cache-specific configuration
110195
+ * @returns LRU cache instance
109916
110196
  */
109917
110197
  getCache(name, config) {
109918
- if (this.caches.has(name)) {
109919
- return this.caches.get(name);
110198
+ const existingCache = this.caches.get(name);
110199
+ if (existingCache) {
110200
+ return existingCache;
109920
110201
  }
109921
- const cache = new Cache(config);
110202
+ const mergedConfig = {
110203
+ ...this.globalConfig,
110204
+ ...config,
110205
+ name
110206
+ };
110207
+ const cache = new LRUCache(mergedConfig);
109922
110208
  this.caches.set(name, cache);
109923
- if (this.debug) {
109924
- console.log(`[CacheManager] Created cache: ${name}`, config);
109925
- }
109926
110209
  return cache;
109927
110210
  }
109928
110211
  /**
@@ -109937,11 +110220,11 @@ var CacheManager = class {
109937
110220
  * Clean expired entries from all caches
109938
110221
  */
109939
110222
  cleanAllExpired() {
109940
- let total = 0;
110223
+ let totalCleaned = 0;
109941
110224
  for (const cache of this.caches.values()) {
109942
- total += cache.cleanExpired();
110225
+ totalCleaned += cache.cleanExpired();
109943
110226
  }
109944
- return total;
110227
+ return totalCleaned;
109945
110228
  }
109946
110229
  /**
109947
110230
  * Get statistics for all caches
@@ -109954,11 +110237,16 @@ var CacheManager = class {
109954
110237
  return stats;
109955
110238
  }
109956
110239
  /**
109957
- * Destroy cache manager
110240
+ * Get list of all cache names
109958
110241
  */
109959
- destroy() {
109960
- this.clearAll();
109961
- this.caches.clear();
110242
+ getCacheNames() {
110243
+ return Array.from(this.caches.keys());
110244
+ }
110245
+ /**
110246
+ * Delete a named cache
110247
+ */
110248
+ deleteCache(name) {
110249
+ return this.caches.delete(name);
109962
110250
  }
109963
110251
  };
109964
110252
  function createCacheManager(config) {
@@ -118451,12 +118739,11 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118451
118739
  {}
118452
118740
  );
118453
118741
  }
118454
- try {
118455
- return await this._requestMintUCDAttempt(request);
118456
- } catch (error2) {
118457
- const errorMsg = error2.message || String(error2);
118742
+ const result = await this._requestMintUCDAttempt(request);
118743
+ if (!result.success && fullRetry < MAX_FULL_RETRIES) {
118744
+ const errorMsg = result.error || "";
118458
118745
  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");
118459
- if (isStaleTimestamp && fullRetry < MAX_FULL_RETRIES) {
118746
+ if (isStaleTimestamp) {
118460
118747
  if (this.config.debug) {
118461
118748
  log.warn(
118462
118749
  `\u26A0\uFE0F Timestamp became stale during mint process. Retrying from beginning...`,
@@ -118466,8 +118753,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118466
118753
  await new Promise((resolve) => setTimeout(resolve, 2e3));
118467
118754
  continue;
118468
118755
  }
118469
- throw error2;
118470
118756
  }
118757
+ return result;
118471
118758
  }
118472
118759
  return {
118473
118760
  success: false,
@@ -119089,6 +119376,7 @@ Context: Quantum timestamp=${validationResponse.timestamp}, Position=${request.p
119089
119376
  "0x131d9a21": "QuantumExpired()",
119090
119377
  "0x52ce5d58": "QuantumAlreadyUsed()",
119091
119378
  "0x137f3b70": "InDeadZone()",
119379
+ "0xbe4b82c1": "DeadZoneViolation()",
119092
119380
  "0x3e76a3c9": "QuantumOutsideWindow()",
119093
119381
  "0x62278171": "InvalidValidatorSignature()",
119094
119382
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119109,7 +119397,7 @@ Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMi
119109
119397
  Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
119110
119398
  ` : "";
119111
119399
  let quantumContext = "";
119112
- if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0x3e76a3c9") {
119400
+ if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0xbe4b82c1" || selector === "0x3e76a3c9") {
119113
119401
  const currentTime2 = Math.floor(Date.now() / 1e3);
119114
119402
  const currentQuantum = Math.floor(currentTime2 / 60) * 60;
119115
119403
  const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
@@ -119371,6 +119659,7 @@ Position: ${request.positionId}`
119371
119659
  validationResponse.timestamp,
119372
119660
  signatureHexMint
119373
119661
  ]);
119662
+ await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
119374
119663
  const fromAddress = await signer.getAddress();
119375
119664
  const estimatedGas = await estimateContractCallGasWithMargin(
119376
119665
  signerProvider,
@@ -119429,6 +119718,7 @@ Position: ${request.positionId}`
119429
119718
  "0x131d9a21": "QuantumExpired()",
119430
119719
  "0x52ce5d58": "QuantumAlreadyUsed()",
119431
119720
  "0x137f3b70": "InDeadZone()",
119721
+ "0xbe4b82c1": "DeadZoneViolation()",
119432
119722
  "0x3e76a3c9": "QuantumOutsideWindow()",
119433
119723
  "0x62278171": "InvalidValidatorSignature()",
119434
119724
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119608,6 +119898,7 @@ Error data: ${errorData || "none"}`
119608
119898
  "0x131d9a21": "QuantumExpired() - Signature quantum window has closed",
119609
119899
  "0x52ce5d58": "QuantumAlreadyUsed() - This quantum was already used for this position",
119610
119900
  "0x137f3b70": "InDeadZone() - Timestamp in dead zone (near quantum boundary)",
119901
+ "0xbe4b82c1": "DeadZoneViolation() - Non-current-quantum signature mined in the last 8s of the current quantum",
119611
119902
  "0x3e76a3c9": "QuantumOutsideWindow() - Timestamp not in past/current/next quantum window"
119612
119903
  };
119613
119904
  const errorName = knownErrors[selector] || `Unknown error ${selector}`;
@@ -120676,6 +120967,7 @@ Error data: ${errorData || "none"}`
120676
120967
  );
120677
120968
  }
120678
120969
  const positionManager = positionManagerResult.value;
120970
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
120679
120971
  const tx = await positionManager["extendPosition"](
120680
120972
  positionIdBytes32,
120681
120973
  BigInt(_selectedTerm),
@@ -121108,6 +121400,57 @@ Error data: ${errorData || "none"}`
121108
121400
  if (!pauseCheck.ok) {
121109
121401
  return { success: false, error: pauseCheck.error };
121110
121402
  }
121403
+ const MAX_FULL_RETRIES = 3;
121404
+ for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
121405
+ if (fullRetry > 1 && this.config.debug) {
121406
+ log.info(
121407
+ `\u{1F504} Full payment retry ${fullRetry}/${MAX_FULL_RETRIES} due to quantum-timing failure...`,
121408
+ {}
121409
+ );
121410
+ }
121411
+ const result = await this._makePaymentAttempt(request);
121412
+ if (fullRetry < MAX_FULL_RETRIES && this.isRetryablePaymentQuantumFailure(result)) {
121413
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
121414
+ continue;
121415
+ }
121416
+ return result;
121417
+ }
121418
+ return {
121419
+ success: false,
121420
+ error: "Max retries exceeded for payment operation"
121421
+ };
121422
+ } finally {
121423
+ this.invalidateCachesForPosition(request.positionId);
121424
+ this.releaseWriteLock(request.positionId);
121425
+ }
121426
+ }
121427
+ /**
121428
+ * Whether a failed payment attempt is a quantum-timing failure that a fresh
121429
+ * re-sign can fix (safe to retry), versus a terminal failure.
121430
+ *
121431
+ * Retryable: pre-send simulation quantum errors, or an atomic mined revert
121432
+ * (status 0) — no funds moved and the quantum was not recorded on-chain, so a
121433
+ * fresh signature + resubmit is safe.
121434
+ *
121435
+ * NOT retryable: a confirmation timeout — the original tx may still be pending, so
121436
+ * a resubmit could double-pay. It is surfaced (with its tx hash) instead.
121437
+ */
121438
+ isRetryablePaymentQuantumFailure(result) {
121439
+ if (result.success || !result.error)
121440
+ return false;
121441
+ const e = result.error;
121442
+ if (e.includes("Transaction timeout"))
121443
+ return false;
121444
+ return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
121445
+ }
121446
+ /**
121447
+ * One payment attempt: user auth → Lit Action authorization → dead-zone gate →
121448
+ * pre-send simulation → broadcast → confirmation. Always resolves to a
121449
+ * PartialPaymentResult (never throws to the caller); the makePayment wrapper owns
121450
+ * the write lock, the pause pre-check, and the bounded re-sign retry loop.
121451
+ */
121452
+ async _makePaymentAttempt(request) {
121453
+ try {
121111
121454
  if (this.config.debug) {
121112
121455
  log.info(`\u{1F4B3} Making payment...`, {});
121113
121456
  log.info(` Request object:`, { request });
@@ -121368,7 +121711,6 @@ Error data: ${errorData || "none"}`
121368
121711
  error: `Failed to get PositionManager: ${positionManagerResult.error.message}`
121369
121712
  };
121370
121713
  }
121371
- const positionManager = positionManagerResult.value;
121372
121714
  if (this.config.debug) {
121373
121715
  log.info(
121374
121716
  ` About to call toBytes32 with: ${request.positionId} (type: ${typeof request.positionId})`
@@ -121428,9 +121770,6 @@ Error data: ${errorData || "none"}`
121428
121770
  currentQuantum
121429
121771
  });
121430
121772
  }
121431
- if (this.config.debug) {
121432
- log.info("\u23ED\uFE0F Skipping dead zone check to isolate BigNumber issue", {});
121433
- }
121434
121773
  if (this.config.debug) {
121435
121774
  log.info("\u{1F50D} Final timestamp validation:", {
121436
121775
  litActionTimestamp: litActionResult.timestamp,
@@ -121497,108 +121836,100 @@ Error data: ${errorData || "none"}`
121497
121836
  quantumTimestamp: quantumTimestamp.toString()
121498
121837
  });
121499
121838
  }
121500
- let tx;
121501
- try {
121502
- if (this.config.debug) {
121503
- log.info("\u{1F50D} Attempting contract interface call to makePayment");
121504
- }
121505
- if (this.config.debug) {
121506
- log.info("\u{1F50D} Contract call parameters:", {
121507
- positionIdBytes32,
121508
- paymentAmountWei: paymentAmountWei.toString(),
121509
- quantumTimestamp: quantumTimestamp.toString(),
121510
- btcPrice: btcPrice.toString(),
121511
- signatureLength: signature.length
121512
- });
121513
- }
121514
- if (typeof quantumTimestamp !== "bigint") {
121515
- throw new Error(
121516
- `quantumTimestamp is not a BigInt: ${typeof quantumTimestamp}, value: ${quantumTimestamp}`
121517
- );
121518
- }
121519
- if (typeof btcPrice !== "bigint") {
121520
- throw new Error(
121521
- `btcPrice is not a BigInt: ${typeof btcPrice}, value: ${btcPrice}`
121522
- );
121523
- }
121524
- if (typeof paymentAmountWei !== "bigint") {
121525
- throw new Error(
121526
- `paymentAmountWei is not a BigNumber: ${typeof paymentAmountWei}, value: ${paymentAmountWei}`
121527
- );
121528
- }
121529
- const paymentAmountStr = paymentAmountWei.toString();
121530
- const quantumTimestampStr = quantumTimestamp.toString();
121531
- const btcPriceStr = btcPrice.toString();
121532
- if (this.config.debug) {
121533
- log.info("\u{1F50D} About to call contract makePayment with:", {
121534
- positionIdBytes32: positionIdBytes32.substring(0, 20) + "...",
121535
- paymentAmountWei: paymentAmountStr,
121536
- quantumTimestamp: quantumTimestampStr,
121537
- btcPrice: btcPriceStr,
121538
- signature: signature.substring(0, 20) + "..."
121539
- });
121540
- }
121541
- if (typeof paymentAmountStr !== "string" || paymentAmountStr === "[object Object]") {
121542
- throw new Error(`Invalid paymentAmount: ${paymentAmountStr}`);
121543
- }
121544
- if (typeof quantumTimestampStr !== "string" || quantumTimestampStr === "[object Object]") {
121545
- throw new Error(`Invalid quantumTimestamp: ${quantumTimestampStr}`);
121546
- }
121547
- if (typeof btcPriceStr !== "string" || btcPriceStr === "[object Object]") {
121548
- throw new Error(`Invalid btcPrice: ${btcPriceStr}`);
121549
- }
121550
- if (this.config.debug) {
121551
- log.info("\u{1F50D} Final parameter validation before contract call:", {
121552
- positionIdBytes32: typeof positionIdBytes32,
121553
- paymentAmountStr: typeof paymentAmountStr + " = " + paymentAmountStr.substring(0, 20),
121554
- quantumTimestampStr: typeof quantumTimestampStr + " = " + quantumTimestampStr.substring(0, 20),
121555
- btcPriceStr: typeof btcPriceStr + " = " + btcPriceStr.substring(0, 20),
121556
- signature: typeof signature + " = " + signature.substring(0, 20)
121839
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121840
+ const paymentAmountStr = paymentAmountWei.toString();
121841
+ const quantumTimestampStr = quantumTimestamp.toString();
121842
+ const btcPriceStr = btcPrice.toString();
121843
+ const paymentContractAddress = this.getContractAddressesOrThrow().positionManager;
121844
+ const paymentSigner = this.getSignerOrThrow();
121845
+ const paymentFrom = await paymentSigner.getAddress();
121846
+ const paymentProvider = contractManager.getProvider();
121847
+ const paymentIface = new Interface([
121848
+ "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121849
+ ]);
121850
+ const paymentCalldata = paymentIface.encodeFunctionData("makePayment", [
121851
+ positionIdBytes32,
121852
+ paymentAmountStr,
121853
+ quantumTimestampStr,
121854
+ btcPriceStr,
121855
+ signature
121856
+ ]);
121857
+ const decodePaymentRevert = (e) => {
121858
+ const raw = e?.data ?? e?.info?.error?.data ?? e?.error?.data?.data ?? e?.error?.data;
121859
+ const data = typeof raw === "string" ? raw : void 0;
121860
+ if (!data || !data.startsWith("0x") || data.length < 10)
121861
+ return null;
121862
+ const selector = data.slice(0, 10);
121863
+ const map3 = {
121864
+ "0xbe4b82c1": "DeadZoneViolation()",
121865
+ "0x137f3b70": "InDeadZone()",
121866
+ "0x52ce5d58": "QuantumAlreadyUsed()",
121867
+ "0x3e76a3c9": "QuantumOutsideWindow()",
121868
+ "0x131d9a21": "QuantumExpired()",
121869
+ "0x8baa579f": "InvalidSignature()",
121870
+ "0x62278171": "InvalidValidatorSignature()",
121871
+ "0x3ee5aeb5": "OperationNotAuthorized()",
121872
+ "0x48f5c3ed": "Unauthorized()"
121873
+ };
121874
+ return map3[selector] ?? `Unknown error ${selector}`;
121875
+ };
121876
+ const MAX_DEADZONE_RESIMULATIONS = 3;
121877
+ for (let sim = 1; ; sim++) {
121878
+ try {
121879
+ await paymentProvider.call({
121880
+ to: paymentContractAddress,
121881
+ from: paymentFrom,
121882
+ data: paymentCalldata
121557
121883
  });
121558
- }
121559
- tx = await positionManager["makePayment"](
121560
- positionIdBytes32,
121561
- paymentAmountStr,
121562
- quantumTimestampStr,
121563
- btcPriceStr,
121564
- signature
121565
- );
121566
- if (this.config.debug) {
121567
- log.info("\u2705 Contract call succeeded", { txHash: tx.hash });
121568
- }
121569
- } catch (contractError) {
121570
- if (this.config.debug) {
121571
- log.warn(
121572
- "\u26A0\uFE0F Contract interface failed, falling back to raw transaction",
121573
- {
121574
- error: contractError instanceof Error ? contractError.message : String(contractError)
121884
+ break;
121885
+ } catch (simError) {
121886
+ const decoded = decodePaymentRevert(simError);
121887
+ if (decoded === "DeadZoneViolation()" && sim <= MAX_DEADZONE_RESIMULATIONS) {
121888
+ const nowSec = Math.floor(Date.now() / 1e3);
121889
+ const sigQuantum = Math.floor(Number(quantumTimestamp) / 60) * 60;
121890
+ const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
121891
+ if (sigQuantum === realCurrentQuantum) {
121892
+ if (this.config.debug) {
121893
+ log.info(
121894
+ "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
121895
+ {}
121896
+ );
121897
+ }
121898
+ break;
121575
121899
  }
121576
- );
121577
- }
121578
- const contractAddress = this.getContractAddressesOrThrow().positionManager;
121579
- const iface = new Interface([
121580
- "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121581
- ]);
121582
- const calldata = iface.encodeFunctionData("makePayment", [
121583
- positionIdBytes32,
121584
- paymentAmountWei.toString(),
121585
- quantumTimestamp.toString(),
121586
- btcPrice.toString(),
121587
- signature
121588
- ]);
121589
- const signer2 = this.getSignerOrThrow();
121590
- tx = await signer2.sendTransaction({
121591
- to: contractAddress,
121592
- data: calldata,
121593
- value: "0x0",
121594
- gasLimit: 5e5
121595
- });
121596
- if (this.config.debug) {
121597
- log.info("\u2705 Raw transaction fallback succeeded", {
121598
- txHash: tx.hash
121599
- });
121900
+ const gate = await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121901
+ if (gate.waited) {
121902
+ if (this.config.debug) {
121903
+ log.info(
121904
+ `\u{1F501} Re-simulating makePayment after crossing the quantum boundary (attempt ${sim}/${MAX_DEADZONE_RESIMULATIONS})`,
121905
+ {}
121906
+ );
121907
+ }
121908
+ continue;
121909
+ }
121910
+ }
121911
+ const label = decoded ?? simError?.reason ?? simError?.shortMessage ?? (simError instanceof Error ? simError.message : String(simError));
121912
+ if (this.config.debug) {
121913
+ log.error("\u274C makePayment pre-send simulation reverted", { error: label });
121914
+ }
121915
+ return {
121916
+ success: false,
121917
+ error: `Payment would revert (pre-send simulation): ${label}`,
121918
+ positionId: request.positionId,
121919
+ paymentAmountUCD: request.paymentAmount
121920
+ };
121600
121921
  }
121601
121922
  }
121923
+ const MAKE_PAYMENT_GAS_CEILING = 500000n;
121924
+ const tx = await sendEip1559Transaction({
121925
+ signer: paymentSigner,
121926
+ to: paymentContractAddress,
121927
+ data: paymentCalldata,
121928
+ gasLimit: MAKE_PAYMENT_GAS_CEILING
121929
+ });
121930
+ if (this.config.debug) {
121931
+ log.info("\u2705 makePayment broadcast", { txHash: tx.hash });
121932
+ }
121602
121933
  if (this.config.debug) {
121603
121934
  log.info(`\u{1F4E4} Transaction sent: ${tx.hash}`);
121604
121935
  log.info("\u23F3 Waiting for transaction confirmation...");
@@ -121742,9 +122073,6 @@ Error data: ${errorData || "none"}`
121742
122073
  result.effectiveGasPrice = receipt.effectiveGasPrice?.toString() || receipt.gasPrice?.toString();
121743
122074
  }
121744
122075
  return result;
121745
- } finally {
121746
- this.invalidateCachesForPosition(request.positionId);
121747
- this.releaseWriteLock(request.positionId);
121748
122076
  }
121749
122077
  }
121750
122078
  /**
@@ -122306,6 +122634,7 @@ Error data: ${errorData || "none"}`
122306
122634
  utxoVout: withdrawalParams.utxoVout
122307
122635
  });
122308
122636
  }
122637
+ await awaitSafeSubmissionWindow(Number(withdrawalParams.quantumTimestamp));
122309
122638
  let tx;
122310
122639
  try {
122311
122640
  tx = await positionManagerContract["withdrawBTC"](